App Manifest
manifest.json is the package contract Shuuka reads at install time and runtime.
Full Example
{
"name": "My App",
"slug": "my-app",
"version": "1.0.0",
"runtime_kind": "smart",
"entry": "index.html",
"supportedFrameworks": ["react"],
"supported_languages": ["en", "de"],
"permissions": ["profile:read", "settings:read", "settings:write"],
"documentation_link": "https://docs.example.com/my-app",
"support_link": "https://example.com/support",
"author_link": "https://example.com",
"api_url": [
"https://api.example.com"
],
"card_settings": {
"card_style": true,
"box_shadow": true,
"full_width": false,
"disable_scroll_overlay": true,
"size": {
"width": "100%",
"height": "auto"
}
},
"localization": {
"default_locale": "en",
"supported_locales": ["en", "de"],
"translation_namespace": "apps.my_app",
"files": {
"en": "lang/en.json",
"de": "lang/de.json"
}
},
"groups": [
{
"id": "content",
"title": "Content"
},
{
"id": "design",
"title": "Design",
"sections": [
{ "id": "layout", "title": "Layout" },
{ "id": "colors", "title": "Colors" }
]
}
],
"placeholders": [
{
"key": "title",
"type": "text",
"required": true,
"default": "Hello Shuuka",
"group": "content"
},
{
"key": "layout",
"type": "select",
"default": "compact",
"options": [
{ "value": "compact", "label": "Compact" },
{ "value": "square", "label": "Square" }
],
"group": "design",
"section": "layout"
}
],
"admin_pages": [
{
"key": "overview",
"title": "Overview",
"entry": "admin/overview.html",
"icon": "chart-bar"
}
],
"admin_detail": {
"instance_selector": {
"enabled": true,
"label_field": "title",
"subtitle_field": "description"
},
"include_data_page": false
},
"bottom_sheet_contract": "Describe how the app opens, renders, and closes inside the Shuuka bottom sheet.",
"privacy": {
"collects_pii": true,
"collects_visitor_data": true,
"gdpr_lawful_basis": "consent",
"data_controller": "profile_owner",
"data_processor": "shuuka",
"data_fields": ["email", "ip_address"],
"data_categories": [
{
"name": "Contact information",
"purpose": "Lead capture",
"retention_days": null
}
],
"requires_visitor_consent": true,
"visitor_consent_text": {
"en": "This app shares visitor data with Example Service.",
"de": "Diese App teilt Besucherdaten mit Example Service."
},
"third_party_transfers": [
{
"url": "https://api.example.com",
"purpose": "Form delivery and submissions",
"country": "US",
"transfer_mechanism": "DPF-certified",
"privacy_policy_url": "https://example.com/privacy",
"data_sent": ["email", "ip_address"]
}
],
"third_party_embeds": [
{
"name": "Example Service",
"url": "https://embed.example.com",
"privacy_policy_url": "https://example.com/privacy"
}
],
"retention_hint": "Controlled by the service provider and the profile owner.",
"retention_days": null
}
}
Minimum Required Fields
| Field | Required | Rule |
|---|---|---|
name | Yes | Marketplace-facing app name |
version | Yes | Semver string |
entry | Yes | Must point to a real file inside the uploaded bundle |
The installer also requires icon.svg, thumbnail.jpg, and header.jpg. Read App package checklist for the file rules.
Top-Level Fields
| Field | Required | Use |
|---|---|---|
slug | Recommended | Stable app identifier |
runtime_kind | Recommended | Usually simple or smart |
supportedFrameworks | No | Builder metadata such as vanilla or react |
supported_languages | No | Languages the package supports |
permissions | No | Declared app capabilities |
documentation_link | No | Public docs URL |
support_link | No | Support URL |
author_link | No | Author URL |
api_url | No | External API domains used by the app |
localization | No | Translation file map for simple and smart apps |
groups | No | Visual grouping for App Instance Settings |
placeholders | No | Per-instance sidebar fields for simple and smart apps |
card_settings | No | App-owned card behavior and layout constraints |
admin_pages | No | Custom owner tabs |
admin_detail | No | Extra admin-detail behavior |
bottom_sheet_contract | No | Required when the app supports bottom-sheet behavior |
privacy | Strongly recommended | Consent, data-processing, and CSP declaration |
screenshots | No | Optional marketplace screenshots list |
card_settings
Use card_settings only when the app must lock a real runtime constraint. Themes should still own the default visual system.
| Key | Type | Possible values | Use |
|---|---|---|---|
card_style | boolean | true, false | Preferred key for whether Shuuka should render card chrome around the app |
style | boolean | true, false | Legacy alias for card_style |
background | string | CSS color, gradient, or transparent | Locks the card background when the app needs a fixed surface. Omit to inherit the platform's theme-aware default (light/dark mode). |
border_radius | string | CSS radius such as 0, 12px, 999px | Locks the card radius |
box_shadow | boolean or string | true, false, or CSS shadow value | Locks card shadow behavior. Omit to inherit the platform default. |
full_width | boolean | true, false | Lets the app stretch edge-to-edge inside theme-controlled layouts |
disable_scroll_overlay | boolean | true, false | Host compatibility flag for apps that manage their own interaction flow |
size | object | global size object | Declares embed sizing rules |
size.width | number or string | pixel value like 360 or host-controlled value like "100%" | Default width |
size.height | number or string | pixel value like 520 or "auto" | Default height |
localization
Use localization when your app ships translation files for a simple or smart app.
| Key | Required | Use |
|---|---|---|
default_locale | Yes | Default language |
supported_locales | Yes | Available locales |
translation_namespace | Recommended | Translation namespace for your strings |
files | Yes | Locale-to-file map |
Example:
{
"localization": {
"default_locale": "en",
"supported_locales": ["en", "de"],
"translation_namespace": "apps.my_app",
"files": {
"en": "lang/en.json",
"de": "lang/de.json"
}
}
}
groups and placeholders
These are mainly for simple and smart HTML apps. They define App Instance Settings, which Shuuka renders in the per-app sidebar for one installed app instance and injects into the app template.
groups
groups organize the App Instance Settings UI.
| Key | Required | Use |
|---|---|---|
id | Yes | Group identifier |
title or title_key | Yes | Group label |
sections | No | Optional subsection list inside the group |
placeholders
placeholders define App Instance Settings fields.
| Key | Required | Use |
|---|---|---|
key | Yes | Setting identifier injected into the template |
type | Yes | Input type such as text, textarea, select, color, number, image, url, switch |
required | No | Validation rule |
default | No | Default value |
label or label_key | Recommended | Field label |
help or help_key | No | Help text |
example or example_key | No | Example or placeholder text |
options | No | Select, radio, or checkbox options |
group | No | Group assignment |
section | No | Section assignment |
show_if | No | Conditional visibility |
min, max, step | No | Numeric constraints |
allow_none | No | Allows empty color or image style values |
Example:
{
"groups": [
{ "id": "content", "title": "Content" },
{
"id": "design",
"title": "Design",
"sections": [
{ "id": "layout", "title": "Layout" }
]
}
],
"placeholders": [
{
"key": "title",
"type": "text",
"required": true,
"default": "Hello Shuuka",
"group": "content"
},
{
"key": "layout",
"type": "select",
"default": "compact",
"options": [
{ "value": "compact", "label": "Compact" },
{ "value": "square", "label": "Square" }
],
"group": "design",
"section": "layout"
}
]
}
admin_pages
Each admin_pages entry becomes an owner-facing custom tab.
| Key | Required | Meaning |
|---|---|---|
key | Yes | Stable page identifier |
title or title_key | Yes | Tab label |
entry | Yes | HTML file inside the uploaded bundle |
icon | No | Optional dashboard icon |
admin_detail
Use admin_detail to change the owner detail-page behavior.
| Key | Required | Use |
|---|---|---|
instance_selector.enabled | No | Show or hide the built-in installation selector |
instance_selector.label_field | No | Which field to use as the installation label |
instance_selector.subtitle_field | No | Which field to use as the installation subtitle |
include_data_page | No | Whether Shuuka should include the built-in Data tab |
Example:
{
"admin_detail": {
"instance_selector": {
"enabled": true,
"label_field": "title",
"subtitle_field": "description"
},
"include_data_page": false
}
}
bottom_sheet_contract
Add this when the app supports bottom-sheet mode. It should describe:
- how the card opens the sheet
- what query param or runtime state the sheet uses
- how the inner experience closes
Example:
{
"bottom_sheet_contract": "The card button sends SHUUKA_APP_BOTTOM_SHEET {action:'open'} to the parent. The sheet reloads the app with a bottom-sheet query param and the app renders the form directly. Close actions send {action:'done'} to dismiss the sheet."
}
Read App SDK UI Helpers for the public bottom-sheet open and close patterns.
privacy
The privacy block is not just descriptive. It directly affects:
- visitor consent behavior
- the CSP allowlist for external requests and embeds
If a third-party domain is missing here, the browser can block the request even if the visitor accepted consent.
privacy Fields
| Field | Required | Use |
|---|---|---|
collects_pii | Yes | true if personal data is collected or sent |
collects_visitor_data | Recommended | true if device, IP, or similar data is processed |
gdpr_lawful_basis | Recommended | Usually consent, legitimate_interest, or contract |
data_controller | Recommended | Usually profile_owner |
data_processor | Recommended | Usually shuuka |
data_fields | Recommended | Flat list of data identifiers |
data_categories | Recommended | Structured categories shown to the user |
requires_visitor_consent | Recommended | Whether consent must be collected before loading |
visitor_consent_text | Recommended | Localized consent copy |
third_party_transfers | Required if contacting external APIs or SDKs | One entry per external origin the app contacts |
third_party_embeds | Recommended for iframe or embed services | Third-party framed or embedded services |
retention_hint | No | Human-readable retention note |
retention_days | No | Retention value if known |
third_party_transfers Entry
| Key | Required | Use |
|---|---|---|
url | Yes | External origin |
purpose | Yes | Why the service is used |
country | Yes | Processing country |
transfer_mechanism | Yes | Legal transfer basis such as DPF-certified, same_country, or Standard Contractual Clauses |
privacy_policy_url | Yes | Privacy-policy link |
data_sent | Yes | Data identifiers sent to that service |
controller_type | No | Optional controller relationship |
third_party_embeds Entry
| Key | Required | Use |
|---|---|---|
name | Yes | Service name |
url | Yes | Embed origin |
privacy_policy_url | Recommended | Privacy-policy link |
Privacy Examples
No third-party transfer:
{
"privacy": {
"collects_pii": true,
"collects_visitor_data": true,
"gdpr_lawful_basis": "legitimate_interest",
"data_controller": "profile_owner",
"data_processor": "shuuka",
"data_fields": ["firstname", "lastname", "email", "message"],
"data_categories": [
{
"name": "Contact information",
"purpose": "Deliver contact messages to the profile owner",
"retention_days": null
}
],
"requires_visitor_consent": false,
"visitor_consent_text": {
"en": "This app delivers contact data to the profile owner through Shuuka."
},
"third_party_transfers": [],
"third_party_embeds": []
}
}
External API transfer:
{
"privacy": {
"collects_pii": true,
"collects_visitor_data": true,
"requires_visitor_consent": true,
"third_party_transfers": [
{
"url": "https://api.example.com",
"purpose": "Hosted form delivery and submissions",
"country": "US",
"transfer_mechanism": "DPF-certified",
"privacy_policy_url": "https://example.com/privacy",
"data_sent": ["email", "ip_address", "form_responses"]
}
],
"third_party_embeds": []
}
}
Iframe or embed service:
{
"privacy": {
"collects_pii": false,
"collects_visitor_data": true,
"gdpr_lawful_basis": "consent",
"third_party_transfers": [],
"third_party_embeds": [
{
"name": "Google Maps",
"url": "https://www.google.com",
"privacy_policy_url": "https://policies.google.com/privacy"
}
]
}
}
Builder Rule for Third Parties
- list every external origin the app contacts
- do not assume one SDK domain is enough if the service also calls an API subdomain
- if the service is framed or embedded, declare the embed origin too
- if a domain is missing, fix the manifest before debugging the app logic
screenshots
Use screenshots if you want to control the marketplace screenshot order.
Example:
{
"screenshots": [
"screenshots/hero.png",
"screenshots/admin-overview.jpg"
]
}
Recommended Split by App Style
| App style | Most relevant manifest parts |
|---|---|
| React bundle app | name, version, entry, supportedFrameworks, permissions, privacy, card_settings, admin_pages |
| simple or smart HTML app | all of the above plus localization, groups, placeholders, bottom_sheet_contract, and sometimes admin_detail |
Next Step
Read App settings and forms, App admin pages, and Release checklist after drafting your manifest.