---
url: >-
https://developer-portal.gainsight.com/docs/custom-widgets/v2/hosting-widgets.md
description: Choose how to host your widget — externally at a URL or in your repository
---
# Hosting Widgets
Your widget content can be hosted in two ways: **externally** (served from a URL you control) or **in your repository** (published by the platform). See [Choosing an Approach](#choosing-an-approach) for guidance on which option fits your workflow. The sections below document the configuration for each. Scripts and stylesheets follow the same hosting model — see [Scripts Overview](scripts-overview), [Stylesheets Overview](stylesheets-overview), and [Repository Layout](project-setup).
## External Hosting
Use the **content block** when your widget is hosted at a public URL. The platform fetches HTML and assets from that URL whenever the widget is needed. See [Content Object](widget-schema#content-object) for the full field reference.
### Cache Strategies
| Value | Behavior |
|-------|----------|
| `"none"` | No caching, content fetched fresh each time |
| `"no-cache"` | Same as none, no caching |
| `"ttl"` | Cache for specified duration (use with `cacheTtlSeconds`) |
| `"permanent"` | Long-term caching |
`"none"` and `"no-cache"` are equivalent — both fetch content fresh on every request. Use either.
When using `"ttl"`, also specify `cacheTtlSeconds`:
```json
"content": {
"endpoint": "https://example.com/widgets/my_widget.html",
"method": "GET",
"cacheStrategy": "ttl",
"cacheTtlSeconds": 300
}
```
### External Hosting Example
```json
{
"widgets": [
{
"version": "2.1.0",
"title": "User Stats",
"type": "my_company_user_stats",
"description": "Shows user activity statistics from an external service",
"category": "analytics",
"containers": ["Left container", "Sidebar"],
"widgetsLibrary": true,
"settings": {
"configurable": false,
"editable": false,
"removable": true,
"shared": true,
"movable": true
},
"content": {
"endpoint": "https://stats.example.com/widgets/user_stats.html",
"method": "GET",
"cacheStrategy": "ttl",
"cacheTtlSeconds": 300
}
}
]
}
```
## Repository Hosting
Use the **source block** when your widget content is kept locally in your repository. The platform publishes the entire directory you specify and transforms asset references to hosted URLs automatically. See [Source Block](widget-schema#source-block) for the full field reference.
**Example:**
```json
{
"source": {
"path": "widgets/my-static-widget",
"entry": "index.html"
}
}
```
### How Static Widgets Work
1. **Directory Upload**: The entire `source.path` directory is published to the platform
2. **HTML Transformation**: The `source.entry` file has asset references automatically transformed to hosted URLs
3. **Relative Path Preservation**: Directory structure is preserved after publishing
**Supported transformations:**
* HTML attributes: `src`, `href`, `data`, `poster` (in ``, ``, `
```
**HTML file built by Vite/React — root-relative paths:**
```html
```
After publishing, all relative paths and root-relative paths are automatically transformed to hosted URLs. Root-relative paths (starting with `/`) are resolved against the widget's published directory, so `/assets/index-abc123.js` is treated the same as `assets/index-abc123.js`.
### Directory Limits
Static widgets have configurable limits to ensure performance:
| Limit | Default | Description |
|-------|---------|-------------|
| Maximum files | 100 | Total file count in the directory |
| Maximum size | 10 MB | Total size of all files combined |
If your widget exceeds these limits, the build will fail with a clear error message.
### Path Security
For security, these restrictions apply to the `source.path` and `source.entry` configuration fields:
* **No path traversal**: `path` and `entry` cannot contain `../`
* **No absolute paths**: `path` and `entry` cannot start with `/`
* **Symlinks skipped**: Symbolic links are ignored during processing
> **Note:** These restrictions apply only to the `source.path` and `source.entry` fields in `extensions_registry.json`. URLs inside your HTML content files (such as `/assets/style.css`) are not restricted — root-relative URLs are automatically transformed to hosted URLs during publishing.
### JavaScript Dynamic Loading Limitation
::: warning Known Limitation
Asset URLs in JavaScript files are **not** transformed. If your JS dynamically loads assets using relative paths, those paths will break after publishing.
:::
**Workarounds:**
1. **Inline the assets**: Use data URLs for small assets
```javascript
// Instead of: const logo = 'images/logo.png';
const logo = 'data:image/png;base64,...';
```
2. **Use a base URL variable**: Pass the widget base URL from HTML to JS
::: warning
`document.currentScript` may be `null` in the widget execution context (scripts run inside Shadow DOM). Test this pattern before relying on it. For a reliable alternative, see [Rendering & DOM](rendering-and-dom#script-context).
:::
```html
```
```javascript
// In app.js
const logo = window.WIDGET_BASE_URL + 'images/logo.png';
```
3. **Preload in HTML**: Reference assets in HTML where they get transformed
```html
```
## Choosing an Approach
| Aspect | External | Repository |
|--------|----------|-----------|
| **Control** | You own the hosting | Platform hosts for you |
| **Updates** | Deploy to your server | Commit and push to trigger auto-publish |
| **Cache control** | Full control via `cacheStrategy` | Platform manages |
| **Asset transformation** | Manual (if needed) | Automatic |
| **Suitable for** | Dynamic or frequently updated widgets | Static or infrequently changed widgets |
## Next Steps
* [Widget Definition Reference](widget-schema) — every field of a widget entry
* [Configurable Widgets](configurable-widgets) — Add form fields for the No-Code Builder
* [Repository Layout](project-setup) — How to organize your repository
* [Template repository](https://github.com/gainsight-hub/widgets-repository-template) — Working examples of both hosting approaches: a [simple HTML widget](https://github.com/gainsight-hub/widgets-repository-template/tree/main/widgets/demo_widget) and a [React widget with Vite build output](https://github.com/gainsight-hub/widgets-repository-template/tree/main/widgets/react_widget)