Custom content lets you create fully interactive applications that run inside entity pages. Your app runs in a sandboxed iframe and can read/write entity data via the REST API.
How it works Seal injects window.SEAL_CONTEXT into your HTML with credentials to access the API.
Context
Your app receives this context automatically:
interface SealContext {
entityId : string ; // Current entity ID
apiToken : string ; // Bearer token for API calls
apiUrl : string ; // API base URL
version : string | null ; // Snapshot version (null = draft)
}
API examples
const { entityId , apiToken , apiUrl } = window . SEAL_CONTEXT ;
const entity = await fetch ( ` ${ apiUrl } /entities/ ${ entityId } ` , {
headers: { Authorization: `Bearer ${ apiToken } ` }
}). then ( r => r . json ());
Development
Start local dev server
Run your app on port 5347 :
Enter development mode
Open a custom content entity in Seal and click Start Development Mode .
Handle context
In dev mode, context is sent via postMessage: let ctx = window . SEAL_CONTEXT ;
window . addEventListener ( 'message' , ( e ) => {
if ( e . data ?. type === 'SEAL_CONTEXT' ) ctx = e . data . payload ;
});
if ( ! ctx ) window . parent . postMessage ({ type: 'REQUEST_CONTEXT' }, '*' );
Your code works in both dev mode (postMessage) and production (injected context) with the pattern above.
Deploy
Create zip
The zip must contain index.html at the root.
Deploy
curl -X POST " $SEAL_URL /api/custom-content/ $ENTITY_ID /deploy" \
-H "Authorization: Bearer $API_KEY " \
-H "Content-Type: application/zip" \
--data-binary @build.zip
Complete example
A minimal app that fetches and displays the current entity:
<! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" />
< title > Custom Content </ title >
</ head >
< body >
< div id = "app" > Loading... </ div >
< script >
let ctx = window . SEAL_CONTEXT ;
// Dev mode: receive context via postMessage
window . addEventListener ( 'message' , ( e ) => {
if ( e . data ?. type === 'SEAL_CONTEXT' ) {
ctx = e . data . payload ;
init ();
}
});
if ( ctx ) {
init ();
} else {
window . parent . postMessage ({ type: 'REQUEST_CONTEXT' }, '*' );
}
function init () {
fetch ( ` ${ ctx . apiUrl } /entities/ ${ ctx . entityId } ` , {
headers: { Authorization: `Bearer ${ ctx . apiToken } ` }
})
. then ( r => r . json ())
. then ( entity => {
document . getElementById ( 'app' ). innerHTML =
`<pre> ${ JSON . stringify ( entity , null , 2 ) } </pre>` ;
})
. catch ( e => {
document . getElementById ( 'app' ). innerHTML =
`<p style="color:red"> ${ e } </p>` ;
});
}
</ script >
</ body >
</ html >
API reference Full REST API documentation
The SDK uses postMessage instead of REST API. Both work, but REST API is recommended for new apps.