Troubleshooting
Solutions to the most common issues builders encounter when developing apps and themes on Shuuka.
Apps
window.__shuukaCtx is undefined
Symptom: window.__shuukaCtx is undefined when your script runs.
Causes and fixes:
| Cause | Fix |
|---|---|
| Script runs before platform injection | Always guard: var ctx = window.__shuukaCtx || {}; |
Script tag placed before the platform script in template.html | The platform injects context before </head>; your inline script in template.html runs after, so context should be available. If not, check placement. |
| Using dev mode but local server is not running | Start your dev server before loading the profile page. |
Always use a safe fallback:
var ctx = window.__shuukaCtx || {};
var shkApi = ctx.shkApi || window.ShuukaApi;
API call returns 401 Unauthorized
Causes and fixes:
| Cause | Fix |
|---|---|
Calling shkApi.admin.* from a public page | admin endpoints require authentication. Only use shkApi.public.* on public/ pages. |
Calling shkApi.admin.* from template.html (profile card) | Profile cards run in a visitor context. Admin calls belong in admin/*.html pages only. |
accessToken expired | Tokens are refreshed with each page load. Do not cache or persist tokens across sessions. |
API call returns 403 Access code required
Cause: The campaign has access code gating enabled and the request is missing a valid token.
Fix: Verify the code first:
shkApi.public.access.verify(code)
.then(r => r.json())
.then(d => {
var token = d.token; // keep in memory only
// then submit with accessToken: token
});
Entry submission returns 409 already_entered
This is not an error. It means the visitor has already submitted for this campaign (same email under the active deduplication mode). Show a friendly message:
if (d.message === 'already_entered') {
showMessage("You've already entered. Thank you!");
}
Template {{placeholder}} tokens not replaced
Causes and fixes:
| Cause | Fix |
|---|---|
Placeholder key does not match manifest.json | Check that the key in your placeholders array exactly matches the {{token}} in template.html. |
| App is a React bundle, not a simple/smart HTML app | React apps do not use {{}} tokens. Read field values through useSettings() or useShuukaFieldValues(). |
Using [[...]] syntax in an app template | [[...]] is for themes. App templates use {{...}}. |
React SDK hooks return null or undefined
Symptom: useShuuka() returns { sdk: null, ... } on first render.
Cause: The platform context arrives via an async shuuka:ctx postMessage after ShuukaProvider mounts. On the first render the context has not arrived yet.
Fix: Guard against null in your component:
function MyApp() {
const { sdk } = useShuuka();
if (!sdk) return <div className="loading">Initializing…</div>;
return <div>Ready</div>;
}
App iframe height is incorrect or cut off
Symptom: The app is clipped or has extra blank space below.
Causes and fixes:
| Cause | Fix |
|---|---|
| App content changes height after initial render | Call sdk.updateHeight() after every state change |
| Fixed height set that does not match content | Use sdk.updateHeight() instead of a fixed height, or call sdk.uiSafe.setHeight(height, true) with the correct value |
| React app renders asynchronously | Call sdk.updateHeight() inside useEffect after the content renders |
autoResize={true} on ShuukaProvider but app manages height manually | Set autoResize={false} and call sdk.updateHeight() manually |
// After any layout change:
sdk.updateHeight();
// After revealing a form with known height:
sdk.uiSafe.setHeight(520, true);
External API requests are blocked by the browser (CSP / CORS)
Symptom: fetch() or XMLHttpRequest to an external service is blocked with a CSP or CORS error.
Cause: The external domain is not declared in manifest.json under privacy.third_party_transfers.
Fix: Add the domain to third_party_transfers:
{
"privacy": {
"third_party_transfers": [
{
"url": "https://api.example.com",
"purpose": "Form delivery",
"country": "US",
"transfer_mechanism": "DPF-certified",
"privacy_policy_url": "https://example.com/privacy",
"data_sent": ["email"]
}
]
}
}
If the service is embedded as an iframe, declare it in third_party_embeds as well.
header.jpg upload fails
Cause: header.jpg must be exactly 1000×263 pixels.
Fix: Resize your image to exactly 1000×263. Other dimensions will be rejected by the upload validator.
The app does not appear after upload
Checklist:
manifest.jsonhasname,version, andentry(all required)- The file referenced by
entryexists in the ZIP icon.svg,thumbnail.jpg, andheader.jpgare presentheader.jpgis exactly1000×263Developer accessis enabled in Settings > App preferences- The uploaded ZIP contains built files, not source files (
package.json,vite.config.js, etc.)
Double shadow / double card effect
Symptom: App cards show two overlapping shadows or two background layers.
Cause: Both the theme wrapper (.shk-theme__app-card) and the iframe container (.billboard-app-container) have visual styles.
Fix (for app developers): If card_settings.box_shadow is not declared in manifest.json, the platform default shadow on the outer wrapper handles it. Do not add a shadow to document.body inside the app iframe.
Fix (for theme developers): Apply all card chrome to .shk-theme__app-card only. Set box-shadow: none !important and background: transparent !important on .billboard-app-container.
Themes
App cards show a solid dark background on dark themes
Symptom: On a dark glass theme (e.g. Nebula), app cards — Contact, Giveaway, Testimonial Slider, etc. — render with a solid opaque dark background instead of the frosted glass look. The same apps look correct on light themes.
Cause: This is a CSS spec behavior (Color Adjust L3), not a CSS bug. When color-scheme: dark is active on :root (via <meta name="color-scheme" content="dark"> or the CSS property) and :root's background-color is transparent, the browser paints the viewport canvas — the layer below the DOM — with the system dark Canvas color (#1c1c1e on macOS Chrome). Setting html, body { background: transparent } makes those DOM elements see-through, which lets the dark canvas show through.
Platform fix (automatic): The platform injects a <style data-platform-canvas-guard> as the very first tag in each transparent dark app iframe's <head>. The guard sets the iframe body background to the theme's card color (var(--shk-host-card-bg, transparent)), so:
- Glass themes keep an effectively transparent body (their card bg is near-zero opacity)
- Solid-card dark themes (e.g. shuuka-dark) fill the body with the actual card color, preventing the white or dark UA canvas from showing through
You do not need to do anything.
If you are still seeing the issue:
| Check | What to look for |
|---|---|
DevTools → app sub-iframe <head> | <style data-platform-canvas-guard> must be the first tag. If missing, contact platform support. |
| Your theme SCSS | You have color-scheme: dark or color-scheme: light on :root or html inside app iframe CSS. Remove it — the platform manages this. |
| Your app HTML | You have <meta name="color-scheme" content="dark"> hardcoded. Remove it — the platform manages the meta tag server-side. |
theme.config.json | meta.color_scheme is not set to "dark". Without this, sub-iframes do not receive theme_mode=dark and the guard may not fire in dark-mode context. |
See Dark Mode and Transparent App Iframes for the full technical explanation.
App iframes are in light mode even on a dark theme
Cause: meta.color_scheme is missing from theme.config.json.
Fix:
{
"meta": {
"color_scheme": "dark"
}
}
CSS custom properties are undefined in dev preview (Vite mode)
Symptom: Card border radius, colors, or shadows are wrong in Vite dev proxy (?dev_theme={id}). They look correct after building dist/.
Cause: In Vite dev mode, src/index.js does not load through the proxy because the /src/ path 404s on the API server. CSS vars are never injected.
Fix: Always provide fallback values in your SCSS that match your theme.config.json defaults:
// ✅ Correct
.shk-theme__app-card {
border-radius: var(--app-card-radius, 16px); // fallback matches config.iframe.border_radius
box-shadow: var(--app-card-shadow, 0 4px 24px rgba(0,0,0,0.20));
}
Social icons missing in dev preview
Cause: Social icon src paths begin with /social_icons/.... Inside the dev preview proxy (origin: api.shuuka.com), these resolve correctly to the API server's public/social_icons/ directory.
Debug steps:
- Open DevTools → Network → filter
social_icons. - Confirm requests return
200fromapi.shuuka.com. - If
404: the icon file is missing from the API server'spublic/social_icons/folder.
Card style not received by app iframes
Symptom: Apps using .shk-host-card do not inherit the theme's card style.
Checklist:
broadcastCardStyle()is called onDOMContentLoaded.- The
window.addEventListener('message', ...)handler responds toshuuka:request_card_style. resolvedCardStyle.cssTextis set before broadcasting.- The iframe selector
iframe.shk-theme__app-iframe, iframe.billboard-app-iframematches the actual iframe elements.
Theme settings changes do not update the live preview
Symptom: The profile owner changes a setting in the dashboard but the theme does not update.
Cause: Your shuuka:contextSnapshot / shuuka:ctx message handler is missing or does not call applyIframeCssVars().
Fix: Ensure your window.addEventListener('message', ...) handler:
- Listens for both
shuuka:contextSnapshotANDshuuka:ctx. - Calls
applyIframeCssVars(cfg.iframe)to update CSS vars. - Calls
broadcastCardStyle()to re-push updated card style to app iframes.
config.iframe not applied in dev preview
Cause: theme.config.json is missing the config.iframe section.
Behavior: When config.iframe is absent, the platform replaces the iframe config with an empty array in dev preview mode. No CSS vars are emitted.
Fix: Always include config.iframe in theme.config.json. See Theme Manifest.
General
How to verify the uploaded bundle is correct
After uploading, install the app or theme on your profile and check:
- Open DevTools → Console: no JavaScript errors
- Open DevTools → Network: no
404for asset files - Open DevTools → Network: no blocked CSP requests to external domains
- Visual: all placeholder tokens are replaced (no
{{title}}visible in the DOM) - Visual:
header.jpg,thumbnail.jpg, andicon.svgappear correctly in the marketplace
How to reset app storage during development
From an admin page:
// Delete a storage key (use with care in production):
shkApi.admin.storage.delete('winner');
Where to check your manifest is valid
The upload page validates the manifest. If the upload fails, check:
manifest.jsonis valid JSON (no trailing commas, no comments)name,version, andentryare all present- The file referenced by
entryexists at the exact path in the ZIP - All required images are present and named exactly (
icon.svg,thumbnail.jpg,header.jpg)