A Q-App is a static web app (HTML/JS/CSS) published on QDN, usually as a zip with index.html at the root, using service type APP. It runs inside Qortal Hub/Mobile/Extension, which injects qortalRequest for wallet, publish, encrypt, and payments. There is no private backend with secrets; identity is the wallet address plus optional registered name, and assets should ship in the zip for offline/CSP safety.

what are q-apps
define q-app
qapp meaning
what is qapp


What is QDN from a developer perspective?

QDN (Qortal Data Network) stores resources indexed on-chain with payloads mostly off-chain and verified by SHA-256. Each resource is addressed by name, service, and optional identifier; for the same triple, the latest publish wins. Q-Apps and WEBSITEs are QDN resources that Hub can render.

qortal data network
qdn for developers
qdn resource model


What is the difference between WEBSITE and APP services?

WEBSITE returns a real 404 for missing file paths and suits visitor-facing static or multi-page sites. APP falls through missing paths to index.html for SPA client routers and interactive apps. Rule of thumb: client-side router and app behaviour → APP; mostly static pages with real 404s → WEBSITE.

website vs app
APP vs WEBSITE
when to use APP service
when to use WEBSITE


Every QDN object is addressed by name (publisher's registered name controlling update rights), service (IMAGE, JSON, APP, WEBSITE, etc.), and optional identifier (distinguishes multiple resources per name+service). Omit identifier or use "default" for the main website/app for a name.

qdn address
name service identifier
how to address a qdn resource
resource coordinates


Do I need a registered name to publish from a Q-App?

Yes. Publishing from a Q-App requires the user to own a registered name; name ownership gates update rights for that name's resources. Call GET_USER_ACCOUNT then GET_ACCOUNT_NAMES, and if there is no name, explain that publish cannot proceed until they register one.

publish without name
registered name required
name required to publish


qortalRequest is the bridge injected by Core/Hub into embedded Q-App pages. It returns a Promise for wallet, QDN read/write, encrypt, payments, navigation, and more. Always wrap calls in try/catch, resolve the function from window then parent then top in nested iframes, and never invent action names—verify against the user's Hub/Core version.

what is qortal.request
qortalRequest bridge
how does qortalRequest work


How do I detect if qortalRequest is available?

Resolve the bridge with window.qortalRequest ?? window.parent?.qortalRequest (and top if nested). If typeof req !== "function", the page is not inside Hub—show preview mode and refuse wallet/payment fakes. Example: async function qr(p){ const r=window.qortalRequest??window.parent?.qortalRequest; if(typeof r!=="function") throw new Error("Open inside Qortal Hub"); return r(p); }

feature detect qortalRequest
check if inside hub
preview mode detection


When should I use qortalRequest vs fetch?

Use qortalRequest for wallet identity, signing, publish, encrypt, payments, and anything personalized. Use relative fetch("/names/foo") inside Hub for anonymous public Core REST reads. fetch cannot see the logged-in wallet; never hardcode http://127.0.0.1:12391 in shipped apps.

fetch vs qortalRequest
relative fetch
when to use REST in q-app


What is the authority order for Q-App API docs?

When sources conflict: (1) the user's running Qortal Core + Hub version, (2) Q-Apps.md in Core, (3) qortal.dev/docs/q-apps, (4) community guides for patterns only. Do not invent parameters; verify action names in qortal.dev or Core q-apps.js.

which docs are authoritative
verify action names
api source of truth


No private backend holding secrets; identity is wallet address plus optional name; ship fonts/JS/assets in the zip (offline and CSP-safe); no required CDN. Prefer single-file HTML for small tools and Vite with a flat zip for production SPAs.

q-app limitations
q-app constraints
can q-apps have a backend


Zip the contents of dist/, not a wrapper folder: index.html must sit at the zip root, with assets/ and manifest.json beside it. Use relative paths (./assets/...), Vite base: "./", case-sensitive [a-z0-9._-] names, and ship fonts/JS in-bundle with no required CDN. Rebuild and re-zip after any HTML or bundler config change.

zip layout
how to zip q-app
flat zip
dist zip structure


Include at least "main": "index.html" plus title, version, and category metadata. The manifest sits at the zip root next to index.html so Hub knows the entry point. Keep paths relative and consistent with the built assets.

manifest.json q-app
q-app manifest fields


Build (npm run build), create a flat zip from dist/, load it in Hub and Preview, then publish under an owned name with service APP. Open via qortal://APP/<Name>. Multi-file APP/WEBSITE publish from inside a Q-App is limited or unavailable in many Hub builds—use Hub UI and verify your version.

publish q-app steps
how to deploy q-app
publish APP service


How do I publish a WEBSITE resource in code?

Build a static site with relative paths, then publish via Hub under an owned registered name with service WEBSITE. Link with qortal://WEBSITE/<Name>/... . If you need SPA routing and heavy qortalRequest throughout, prefer APP instead of WEBSITE.

publish WEBSITE service programmatically


Why is my Q-App a grey screen or stuck on Loading?

Usually assets/index-*.js 404s: Hub opened without trailing slash so ./assets resolves wrong, or you zipped a dist/ wrapper, used absolute /assets/ paths, or shipped a stale hashed build. Fix with flat zip, base: "./", relative ./assets/, full rebuild, and trailing slash or careful <base href>. Some builds also fail on crossorigin script tags—remove if needed.

grey screen q-app
eternal loading
assets 404
blank q-app


Set Vite base: "./" so built asset URLs are relative. Absolute /assets/... paths break under Hub render URLs like /render/APP/YourName without a trailing slash. After changing base, rebuild and create a fresh flat zip.


Avoid <base href> unless you deliberately fix APP path resolution when Hub serves without a trailing slash. Prefer relative ./assets/ paths and ensuring the app URL has a trailing slash. Mis-set base can break deep links and asset loading.


Can I publish multi-file WEBSITE or APP from inside a Q-App via qortalRequest?

In many Hub builds, publishing multi-file WEBSITE/APP from qortalRequest is limited or unavailable; use Hub UI for those zips. Single-file or data64 PUBLISH_QDN_RESOURCE is supported. Fetching multi-file resources with filepath is supported—verify your target Core/Hub version.

publish multi-file from q-app
PUBLISH WEBSITE from code


What is the publish workflow after building?

1) npm run build → dist/ 2) (cd dist && zip -r ../MyApp.zip .) 3) Hub → load zip → Preview 4) Publish service APP (or WEBSITE via Hub) 5) Open qortal://APP/<Name> or qortal://WEBSITE/<Name>/. Always Preview before QDN publish.

build and publish workflow
preview before publish


Hub expects the APP/WEBSITE entry at the root of the zip (manifest main: index.html). Wrapping files in a dist/ folder inside the zip makes Hub miss the entry and assets 404. Zip the contents of dist/, not dist itself.


JSON holds small structured metadata on QDN, typically capped around 25 KB. Publish with PUBLISH_QDN_RESOURCE using UTF-8-safe data64. For larger structured data use DOCUMENT or FILE. Always validate schema on fetch—never trust identifier alone.

JSON service qdn
25kb json limit
when to use JSON service


DOCUMENT stores larger text/JSON documents with a much higher size allowance than JSON (request caps can reach hundreds of MB depending on Core). Use DOCUMENT when metadata outgrows the ~25 KB JSON limit. DOCUMENT_PRIVATE (801) is the encrypted variant for recipients.

DOCUMENT service
large json document qdn


IMAGE and THUMBNAIL store media on QDN (typical documented limits ~10 MB for IMAGE and ~500 KB for THUMBNAIL). Fetch via FETCH_QDN_RESOURCE or GET_QDN_RESOURCE_URL for <img src>. Shared convention: THUMBNAIL + identifier qortal_avatar for user avatars.

IMAGE service
THUMBNAIL service
avatar thumbnail


What are FILE, VIDEO, and AUDIO services?

FILE, VIDEO, and AUDIO hold binary payloads on QDN for downloads and media apps (e.g. Q-Share/Q-Tube patterns). Publish as single-file/data64 resources; fetch with FETCH_QDN_RESOURCE or resource URLs. Prefer SAVE_FILE on mobile instead of relying on blob anchor downloads.

FILE service
VIDEO service
AUDIO service
binary qdn services


APP multi-file zips are commonly limited around 50 MB (check your Core docs). Keep bundles lean: no CDN, tree-shake, and avoid shipping unused assets. WEBSITE is also multi-file; both need filepath when fetching individual files.


CHAIN_COMMENT is a tiny on-chain payload service (documented around 239 bytes). Use it only for very small on-chain notes, not for app bundles or media. Prefer JSON/DOCUMENT/FILE for normal app data.

CHAIN_COMMENT service
on-chain comment size


DOCUMENT_PRIVATE (numeric service 801 in some docs) stores encrypted documents for recipients. Decrypt with DECRYPT_DATA or group decrypt variants via the wallet. Keep private blobs in separate identifier namespaces from public metadata.

DOCUMENT_PRIVATE
service 801
encrypted document service


When is a resource single-file vs multi-file?

JSON, IMAGE, FILE, VIDEO, AUDIO and similar services carry one payload (data64 or file). WEBSITE, APP, and GIF_REPOSITORY are multi-file; FETCH_QDN_RESOURCE requires filepath to load a specific file. Choose single-file services for records; use APP/WEBSITE for UI bundles.

single-file vs multi-file
filepath required
multi-file services


What are BLOG_POST, MAIL, and MESSAGE services?

These are ecosystem-oriented QDN services used by apps like Q-Blog and Q-Mail (MAIL/MAIL_PRIVATE, MESSAGE, BLOG_POST). Follow each app's identifier conventions and validate payloads. Size and schema vary by app—study reference repos before publishing into those namespaces.

BLOG_POST service
MAIL service
MESSAGE service


Q-Mail uses MAIL/MAIL_PRIVATE; Q-Share uses FILE/DOCUMENT; Q-Tube uses VIDEO/BLOG_POST; Q-Shop uses STORE/PRODUCT plus payments. Study their GitHub zips for packaging and qortalRequest patterns before inventing parallel conventions.

q-mail services
q-tube services
reference q-apps


Prefix identifiers with your app slug, e.g. myapp_config_v1 or myapp_post_<id>. QDN is permissionless—validate payload schema and never trust identifier alone. Use versioned schema strings in JSON (myapp-index-v1) and document the scheme in your README.

identifier naming
appslug prefix
identifier convention


For a name's main website or app, omit identifier or use "default". In qortal:// paths you can include default when you need explicit no-identifier semantics. Shared cross-app locations use well-known identifiers like qortal_avatar.


User avatars commonly use service THUMBNAIL with identifier qortal_avatar under the user's registered name. Fetch with FETCH_QDN_RESOURCE or GET_QDN_RESOURCE_URL for display. Always handle missing avatars gracefully.

avatar convention for apps
fetch qortal_avatar in code


How do I get names for a wallet address?

After GET_USER_ACCOUNT, call GET_ACCOUNT_NAMES with the address (and limit/offset). Use the primary name for publishing. Example: await qr({ action: "GET_ACCOUNT_NAMES", address: acc.address, limit: 10, offset: 0 }).

GET_ACCOUNT_NAMES
list names for address
primary name


GET_NAME_DATA returns name owner and metadata for a registered name. Use it to verify ownership or show profile info before trusting a publisher name. Combine with payload schema validation—name alone does not prove content quality.


How do latest publishes work for the same resource?

For a given name + service + identifier triple, the latest publish wins. Updates overwrite prior payload for that coordinate. Design identifiers so each logical object has a stable key, or version identifiers when you need immutable history.

latest publish wins
update qdn resource
overwrite resource


How do I search resources by identifier prefix?

Use SEARCH_QDN_RESOURCES with prefix: true and a minimal payload (service + identifier prefix). Keep optional fields stripped if search returns empty mysteriously. Normalize the response to an array—cores may wrap results in resources/results/entries/hits/data.

prefix search qdn
SEARCH_QDN_RESOURCES prefix
identifier prefix search


Why do unprefixed identifiers cause problems?

Global QDN namespaces collide easily. Unprefixed ids make discovery and ownership intent unclear and invite cross-app confusion. Always use appslug_type_id style identifiers and validate every fetched JSON schema.

unprefixed identifiers
identifier collision


How should multi-app ecosystems structure resources?

Publish a WEBSITE for visitors, separate APP tools, and small JSON/DOCUMENT indexes for discovery. Use stable identifier prefixes, versioned schema strings, SEARCH_QDN_RESOURCES with prefix: true, and open partners via qortal:// or LINK_TO_QDN_RESOURCE. Never assume identifier proves origin.

multi-app ecosystem
inter-app integration
discovery json


What FILE identifier pattern do blog/mail apps use?

File attachments in blog/mail-style apps often use FILE with identifiers like qfile_…. Follow the host app's documented prefix so other clients can find attachments. Validate mime/size from GET_QDN_RESOURCE_PROPERTIES before rendering.

qfile identifier
file attachment identifier


How do I authenticate the user in a Q-App?

Call GET_USER_ACCOUNT from an explicit Connect/Authenticate button after a user gesture—never on first paint. It returns address and publicKey and shows an approval popup. Opening built index.html in a normal browser cannot complete wallet auth.

GET_USER_ACCOUNT
connect wallet q-app
authenticate q-app
login q-app


Why shouldn't I call GET_USER_ACCOUNT on page load?

GET_USER_ACCOUNT is gesture-gated and shows a user approval popup. Auto-calling on load causes popup fatigue, failed approvals, and poor UX. Put it behind a Connect button, then fetch names and gate publish.

auto connect bad
auth on load
gesture gated auth


What if my Q-App runs in a nested iframe and auth fails?

Resolve qortalRequest from window → parent → top. Parent pages may need a message relay that forwards qortalRequest to Hub. If parent === top and only a stub exists, or inner auth still fails, open the target in a new Hub tab.

iframe qortalRequest
nested iframe auth
message relay hub


Which qortalRequest actions require user approval?

Approval-gated examples include GET_USER_ACCOUNT, wallet info/balance variants, PUBLISH_QDN_RESOURCE, PUBLISH_MULTIPLE_QDN_RESOURCES, SEND_COIN, JOIN_GROUP, DEPLOY_AT, CREATE_POLL/VOTE_ON_POLL, SAVE_FILE, list item mutations, and friends/profile writes. Batch reads; keep approval calls behind clear user actions.

approval popup actions
which actions need consent


Which qortalRequest actions are read-only without approval?

Examples: GET_ACCOUNT_DATA, GET_ACCOUNT_NAMES, GET_NAME_DATA, GET_BALANCE, FETCH_QDN_RESOURCE, LIST_QDN_RESOURCES, SEARCH_QDN_RESOURCES, GET_QDN_RESOURCE_STATUS/METADATA/PROPERTIES/URL, SEARCH_NAMES, SEARCH_TRANSACTIONS, FETCH_BLOCK, group/AT list reads, GET_PRICE. Prefer these for background refresh.

read-only qortalRequest
no approval actions


If qortalRequest is missing on window and parent, show "Preview mode" and allow mock reads only. Never fake successful payments or publishes. Prompt the user to open the app inside Qortal Hub for live APIs.


No. Relative fetch hits the node REST API anonymously and cannot identify the logged-in wallet. Use qortalRequest GET_USER_ACCOUNT (and related wallet actions) for anything personalized or signing-related.

fetch logged in user
anonymous fetch limitation


Common host variables include window._qdnTheme ("light"|"dark") and window._qdnContext (e.g. "gateway"). Use them to theme UI and adapt behavior when running in gateway vs full Hub contexts.


Use FETCH_QDN_RESOURCE with name, service, and optional identifier; add filepath for multi-file APP/WEBSITE; set encoding: "base64" when you need binary-safe data. Example: await qr({ action: "FETCH_QDN_RESOURCE", name, service: "JSON", identifier: id }).

FETCH_QDN_RESOURCE
load qdn resource
fetch resource example


Own a name, UTF-8-encode JSON to data64 (never raw btoa on Unicode), then PUBLISH_QDN_RESOURCE. Example: await qr({ action: "PUBLISH_QDN_RESOURCE", name, service: "JSON", identifier: id, data64: b64Utf8(obj) }). Await user approval and handle cancel vs errors.

PUBLISH_QDN_RESOURCE
publish json example
publish data64


How do I safely base64-encode UTF-8 JSON for publish?

Never btoa(JSON.stringify(obj)) on Unicode. Use TextEncoder: const bytes=new TextEncoder().encode(JSON.stringify(obj)); let bin=""; for(const b of bytes) bin+=String.fromCharCode(b); return btoa(bin);. Decode symmetrically when reading base64 text.


Call SEARCH_QDN_RESOURCES with a minimal payload (service, optional name/identifier, prefix: true for prefixes). Normalize array-or-wrapper responses. If results are mysteriously empty, strip unknown optional keys—extra fields can yield [].

SEARCH_QDN_RESOURCES
search qdn
normalize search results


LIST_QDN_RESOURCES lists by name/service with richer filters than search in some builds. Example: await qr({ action: "LIST_QDN_RESOURCES", name: "Qortino", service: "FILE", limit: 20 }). Prefer listing when you know the publisher name.


GET_QDN_RESOURCE_URL returns a URL suitable for <img src> or fetch (often /arbitrary/...). Use it for media display without manually building paths. On mobile, append ?attachment=true when you need native download handling.


GET_QDN_RESOURCE_STATUS reports build/load status such as READY and percent loaded. Probe status without downloading the full payload—useful before playing VIDEO/AUDIO or large FILE resources.

GET_QDN_RESOURCE_STATUS
resource ready status
percent loaded


GET_QDN_RESOURCE_METADATA returns title, tags, category and similar metadata without fetching the full payload. Use it for cards, search UIs, and galleries. Pair with PROPERTIES for filename/mimeType/size.

GET_QDN_RESOURCE_METADATA
resource metadata
title tags category


GET_QDN_RESOURCE_PROPERTIES returns filename, mimeType, and size for a resource. Use before download/preview to show size and choose renderers. Validate mime types from untrusted publishers.


PUBLISH_MULTIPLE_QDN_RESOURCES batch-publishes several resources to reduce approval rounds. Feature-detect availability for the user's Hub version. Prefer batches for related metadata+thumbnail publishes behind one clear user action.


How do I normalize SEARCH_QDN_RESOURCES results?

Cores may return a bare array or an object with resources, results, entries, hits, or data arrays. Coerce: const arr = Array.isArray(r) ? r : (r?.resources ?? r?.results ?? r?.entries ?? r?.hits ?? r?.data ?? []);. Always Array.isArray-check before map.

normalize search array
search results wrapper


Format: qortal://{service}/{name}/{identifier?}/{path?}. Examples: qortal://WEBSITE/MySite, qortal://WEBSITE/MySite/gallery, qortal://APP/MyQApp, qortal://THUMBNAIL/MyName/qortal_avatar. Use OPEN_NEW_TAB or LINK_TO_QDN_RESOURCE to open them in Hub.

qortal:// protocol
qortal deep link
qortal url format


How do I open another QDN resource from my app?

Prefer LINK_TO_QDN_RESOURCE or OPEN_NEW_TAB with a qortal:// URL. For Hub tab switches, postMessage SET_TAB with { action: 'SET_TAB', requestedHandler: 'UI', payload: { service, name, identifier?, path? } }. Avoid raw window.open to localhost.


Call QDN_RESOURCE_DISPLAYED so Hub's address bar/copy-link matches the sub-path the user sees: { action, service, name, path? }. Useful for multi-page WEBSITE SPAs. Optionally keep <link rel="canonical"> with matching qortal://WEBSITE/<name>/<slug>.

QDN_RESOURCE_DISPLAYED
copy link hub
address bar sync


Use OPEN_PROFILE via qortalRequest to open a peer's Hub profile UI. Pass the identifiers your Hub version expects (verify docs). Prefer this over inventing custom profile routes when integrating social features.


SET_TAB_NOTIFICATIONS sets a badge count on the Hub tab for your app. Use sparingly for unread counts; clear when the user views items. Behavior is Hub-dependent—feature-detect and fail soft.


Published apps often run under /render/WEBSITE/<name>/ or /render/APP/<name>/. Use pathname segments with history.pushState/popstate for in-app navigation without leaving Hub. Preserve intentional hash fragments when cleaning pathnames with replaceState.

spa routing hub
pushState q-app
render path


SPAs may use location.hash for anchors (e.g. #section-id). If you use multiple prefixes like #folder- and #folderdir-, parse longest match first—naive startsWith('folder-') breaks on substrings. Preserve hashes across history.replaceState cleanup.

hash deep links
overlapping hash prefixes


NAVIGATION_HISTORY integrates back/forward with Hub (availability is Hub-dependent). Use it when your SPA should participate in Hub navigation chrome. Always feature-detect and provide in-app back affordances as fallback.


How do I encrypt data with qortalRequest?

Use ENCRYPT_DATA with file or base64 payload; pass optional publicKeys for group encryption. Store ciphertext under separate identifier namespaces from public metadata. Explain the encrypt action to the user before calling.

ENCRYPT_DATA
encrypt qortal
group publicKeys encrypt


How do I decrypt data with qortalRequest?

Use DECRYPT_DATA with ciphertext base64 and the counterparty publicKey. Group variants ENCRYPT_QORTAL_GROUP_DATA / DECRYPT_QORTAL_GROUP_DATA handle group-scoped encryption. Handle user cancel and wrong-key errors distinctly.

DECRYPT_DATA
decrypt qortal
group decrypt


Should encrypted and public data share identifiers?

No. Use separate identifier namespaces for encrypted blobs versus public JSON indexes. That prevents accidental public fetch of private coordinates and clarifies schema. Still validate decrypted payloads before use.

encrypted identifier namespace
private vs public identifiers


QORT values from GET_PRICE and many transactions are integers scaled by 1e8. Divide by 1e8 to display QORT, and multiply when constructing amounts. Confirm field names on your Hub/Core version before SEND_COIN.


Use SEND_COIN / SEND_PAYMENT / TRANSFER_ASSET only after explaining amounts and recipients; these require user approval. Confirm parameters against the target Hub version. Treat payment success and any follow-up notification as separate steps—payment can succeed while notify fails.


How do I sign and broadcast a transaction?

Use SIGN_TRANSACTION for a Hub-signed transaction, then broadcast via REST /transactions/process. Keep signing behind an explicit user action and never handle seed phrases. Verify the exact payload shape for your Core version.

SIGN_TRANSACTION
broadcast transaction
transactions/process


CREATE_POLL requires pollOptions as an array (e.g. objects with optionName), not a string, and pollName must be unique. VOTE_ON_POLL casts a vote with user approval. Validate options client-side before calling.


LIST_GROUPS, JOIN_GROUP, and GET_GROUPS_WITH_MEMBER manage membership. JOIN_GROUP requires approval. Use groups with ENCRYPT_QORTAL_GROUP_DATA when sharing private payloads among members. Feature-detect and handle cancel.


Why is SEARCH_TRANSACTIONS message often missing?

Payment matching via SEARCH_TRANSACTIONS often lacks a reliable message field. Match by reference, type, amounts (1e8 scale), and parties instead of free-text memo. Design payment protocols that do not depend on message alone.

SEARCH_TRANSACTIONS
payment matching
transaction message missing


GET_LIST_ITEMS, ADD_LIST_ITEMS, and DELETE_LIST_ITEM manage per-user lists in Hub UI state—not global QDN. Use for bookmarks/favorites local to the user. Persist shared data on QDN JSON/FILE instead of lists when others must read it.


How do I save files on Qortal Go / mobile?

Mobile WebViews often ignore anchor/blob downloads. Use SAVE_FILE with blob + filename, or /arbitrary/…?attachment=true on builds with a native download listener. Detect mobile via user-agent when choosing download strategy.

SAVE_FILE
mobile download
attachment=true
qortal go download


How should I handle data larger than the JSON 25 KB limit?

Do not stuff oversized objects into JSON. Split: small JSON metadata (index, titles, pointers) plus FILE/DOCUMENT/IMAGE/VIDEO payloads under related identifiers. For very large content, chunk into multiple FILE parts with a versioned index JSON describing order and hashes.

chunking qdn
json too large
split metadata and payload


What is a good chunking pattern for large files?

Publish a JSON index (myapp_file_<id>_meta_v1) with part count, sizes, and SHA-256 hashes, then publish parts as FILE identifiers myapp_file_<id>_part_0001…. Fetch status before each part; reassemble client-side; verify hashes. Bound concurrency—avoid Promise.all on hundreds of fetches.

chunked file publish
multipart qdn
file parts pattern


Why avoid Promise.all on hundreds of QDN fetches?

Unbounded concurrency overwhelms Core/Hub, triggers timeouts, and freezes UI. Use a small worker pool (e.g. 3–6) with retries and backoff. Prefer GET_QDN_RESOURCE_STATUS before heavy FETCH on large media.

bounded concurrency
promise.all pitfall
fetch pool


How should I handle qortalRequest timeouts?

Timeouts are per-action in Core q-apps.js. Fix payload size and batching rather than wrapping everything in huge timeouts. Prefer Core-managed timeouts; qortalRequestWithTimeout exists but letting Core manage limits is more futureproof. Use longer helpers only for publish/payment UX.


Test root URL, deep links, no-name wallet (publish gated), Connect gesture, publish then fetch after propagation delay, search normalization, and grey-screen asset loads. Use Hub Preview before QDN publish; local preview may be http://localhost:12391/render/APP/<Name>?preview=true when Core supports it.

test q-app
q-app testing checklist
preview=true


After zip: open in Hub Preview, confirm Network shows assets/index-*.js as 200, navigate SPA routes, hard-refresh, and re-open via qortal://APP/<Name>. Catch absolute /assets/ paths and stale hashed filenames before publishing.


Cover: user cancels GET_USER_ACCOUNT; wallet with zero names; nested iframe without relay; opening index.html outside Hub (preview mode). Ensure publish buttons stay disabled without a name and errors are human-readable.


Never collect seed phrases or private keys. Treat all QDN JSON as untrusted—validate schema and sizes. Explain publishes, payments, and decrypt before calling qortalRequest. Sanitize any HTML from QDN to prevent XSS. Rate-limit retries; distinguish user cancel vs technical errors.

q-app security
never ask for seed
xss qdn html


Anyone can publish under their own name; fetched HTML/Markdown can contain scripts. Escape or sanitize before innerHTML. Prefer text content or a vetted sanitizer. Never eval remote JS from QDN.


How do relative fetch calls work inside Hub?

Inside Hub, fetch("/names/foo") hits the user's node API—good for public reads (blocks, names, arbitrary endpoints). Do not hardcode 127.0.0.1:12391. For personalized or signing flows use qortalRequest instead.


What is the difference between QDN search mode ALL and LATEST?

REST QDN search modes mode=ALL vs mode=LATEST change whether you see historical publishes or only latest per coordinate. Wrong mode causes duplicates or wrong counts. Prefer LATEST for current app state unless you need history.


Use https://api.qortal.org/api-documentation/ for node REST fields. Cross-check field names when debugging bridge vs REST mismatches. For qortalRequest actions, prefer https://qortal.dev/docs/q-apps and Core Q-Apps.md.


Authoritative qortalRequest reference: https://qortal.dev/docs/q-apps. Protocol: https://github.com/Qortal/qortal/blob/master/Q-Apps.md. Core implementation/timeouts: q-apps.js in Qortal/qortal. REST: api.qortal.org. Community code: gitea.qortal.link and GitHub qortal org.

qortal.dev q-apps
Q-Apps.md
official docs


What GitHub repos should Q-App developers study?

Qortal/qortal (Core, Q-Apps.md, q-apps.js), Qortal/Qortal-Hub, optional Qortal/qapp-core helpers, and reference apps q-mail, q-share, q-tube, q-shop. Community projects also live on https://gitea.qortal.link.

q-app github repos
qapp-core
gitea.qortal.link


Qortal/qapp-core is an optional npm helper library for Q-App development. It can wrap common bridge patterns, but you should still understand raw qortalRequest and verify actions against your Hub/Core version.


How do I publish a thumbnail with metadata?

Publish IMAGE or THUMBNAIL for the media and a small JSON record with title, tags, category, and pointer identifiers. Or set metadata fields supported by your publish action and read them later via GET_QDN_RESOURCE_METADATA. Keep JSON ≤25 KB; put heavy media in IMAGE/VIDEO/FILE.

publish thumbnail metadata
metadata with image


How do I show a resource thumbnail in the UI?

Resolve a URL with GET_QDN_RESOURCE_URL for the THUMBNAIL/IMAGE coordinate, or fetch base64 and build a blob URL. Check GET_QDN_RESOURCE_STATUS before display for large media. Provide a placeholder when missing.


How do I read title and tags without downloading the file?

Call GET_QDN_RESOURCE_METADATA for title, tags, and category without the full payload. Use GET_QDN_RESOURCE_PROPERTIES for filename, mimeType, and size. Ideal for galleries and search result rows.


What is the recommended new Q-App workflow?

Choose single HTML vs Vite/React; scaffold index.html + manifest with relative paths; centralize qortalRequest; Connect → GET_USER_ACCOUNT → GET_ACCOUNT_NAMES → gate publish; design identifier/schema; read with FETCH/SEARCH (+normalize); write with UTF-8 data64; test edge cases; flat zip → Hub Preview → publish APP.


How should I structure a central bridge module?

Export one qr(payload) that resolves window/parent/top qortalRequest, wraps try/catch, and optionally short vs long timeout helpers for publish/payments. All features call this module so iframe/mobile differences stay in one place.

bridge wrapper pattern
central qortalRequest module


What is a minimal Connect then publish pattern?

On Connect: account = GET_USER_ACCOUNT; names = GET_ACCOUNT_NAMES; pick primary name. On publish: PUBLISH_QDN_RESOURCE with owned name, service, identifier, data64. Then FETCH_QDN_RESOURCE to verify. Disable publish when no name exists.

connect then publish
minimal publish pattern


After PUBLISH_QDN_RESOURCE, fetches/search may lag briefly. Retry FETCH or STATUS with backoff, or poll GET_QDN_RESOURCE_STATUS until READY. Do not assume instant global visibility; design UI for "publishing…" states.

publish propagation delay
wait until READY


How do I feature-detect optional publish batching?

Try PUBLISH_MULTIPLE_QDN_RESOURCES inside try/catch; on failure fall back to sequential PUBLISH_QDN_RESOURCE with clear progress. Do not invent parameters—check qortal.dev for the user's version.


What causes SEARCH_QDN_RESOURCES to return empty unexpectedly?

Unknown or extra JSON keys on the search payload can yield empty results on some cores. Use a minimal payload, enable prefix: true only when intended, and normalize wrapped responses. Also confirm service/name/identifier spelling and case.

empty search results
extra fields search


What is the SPA asset trap under /render/APP?

Hub may open /render/APP/YourName without a trailing slash, so ./assets/index.js resolves against /render/APP/ and 404s. Fix with trailing slash, careful <base href>, or paths matching how Hub serves the resource. Rebuild after HTML changes.

trailing slash app
render APP assets 404


Why shouldn't Q-Apps depend on external CDNs?

Hub CSP, offline use, and gateway contexts often block or break CDN scripts/fonts. Bundle JS/CSS/fonts inside the zip with relative paths so the app works offline and consistently across desktop and mobile Hub.


What if payment succeeded but notification failed?

Treat payment and notification as separate steps. Persist local state that payment confirmed (tx signature/reference) even if a follow-up QDN publish or chat notify fails, then retry notify without charging again.

payment notify failed
separate payment steps


Why are case-sensitive paths important in zips?

QDN/Hub serving is case-sensitive. Mismatched Asset vs asset paths work on some desktop FS previews and fail in Hub. Prefer lowercase [a-z0-9._-] filenames and matching import paths from the bundler.


What does a minimal qortalRequest helper look like?

async function qr(payload){ const req=window.qortalRequest??window.parent?.qortalRequest; if(typeof req!=="function") throw new Error("Open inside Qortal Hub"); return req(payload); } Always try/catch at call sites. Extend resolution to top for nested iframes.


How do I load demo JSON after connect in a skeleton app?

After Connect obtains a primary name, PUBLISH_QDN_RESOURCE a small JSON with identifier myapp_demo_<timestamp> and data64 from b64Utf8, then FETCH_QDN_RESOURCE the same coordinates and display the result. Disable the load/publish button until a name exists.


Read window._qdnTheme ("light"|"dark") and style accordingly so the app matches Hub chrome. Re-check on visibility changes if Hub can toggle theme while open. Avoid assuming dark mode defaults.


How do I keep Hub copy-link accurate for WEBSITE subpages?

On each route change, call QDN_RESOURCE_DISPLAYED with service WEBSITE, name, and path. Optionally update canonical qortal://WEBSITE/<name>/<slug>. Without this, copy-link may point only at the site root.

copy-link subpath
website path displayed


Should Connect buttons meet mobile touch targets?

Yes. Use adequate tap targets (e.g. min-height ~44px) for Connect, Publish, and payment buttons. Mobile Hub and Qortal Go are primary surfaces—avoid hover-only UX and tiny icon-only critical actions.


GIF_REPOSITORY is a multi-file QDN service type (like APP/WEBSITE) requiring filepath on fetch. Use it for gif packs rather than APP UI. Confirm size limits and publish path via Hub for your Core version.


Q-Shop-style apps use STORE/PRODUCT services plus payment actions. Follow Q-Shop conventions for identifiers and JSON schemas rather than inventing incompatible shapes if you want ecosystem interoperability. Always validate foreign product JSON.

STORE service
PRODUCT service
q-shop services


Use PUBLISH_QDN_RESOURCE with service IMAGE, owned name, identifier, and file or data64 payload within size limits (~10 MB typical). Pair with JSON metadata for title/alt. Fetch via GET_QDN_RESOURCE_URL for display.


How do I publish a VIDEO or AUDIO resource?

Publish service VIDEO or AUDIO with file/data64 under a namespaced identifier, keep a small JSON index for title/thumbnail pointers, and probe GET_QDN_RESOURCE_STATUS before playback. Use SAVE_FILE or attachment URLs for downloads on mobile.

publish video
publish audio
media qdn publish


PUBLISH_QDN_RESOURCE with service FILE, owned name, identifier (e.g. myapp_file_<id>), and payload. Expose downloads via GET_QDN_RESOURCE_URL; on mobile prefer SAVE_FILE. Store filename/mime in metadata or rely on PROPERTIES.


GET_ACCOUNT_DATA returns the account record for an address (read-only, typically no approval). Use it for public account fields after you know an address from GET_USER_ACCOUNT or a name lookup.


GET_BALANCE reads a balance for an address without implying the logged-in wallet session. For the user's wallet-specific balance UI, also consider approval-gated wallet balance actions on your Hub version. Remember 1e8 scaling for QORT display.


What chain helpers exist via qortalRequest?

Common helpers include FETCH_BLOCK, FETCH_BLOCK_RANGE, GET_BLOCK_HEIGHT, GET_PRICE, DEPLOY_AT, GET_AT, GET_AT_DATA, LIST_ATS, and day summary reads. Verify names/params for your Core; wrap in try/catch and prefer relative REST fetch for simple public chain reads.


Batch read-only calls freely; group approval-needing calls behind one explicit user action (Connect, Publish, Pay). Prefer PUBLISH_MULTIPLE_QDN_RESOURCES when available. Never spam GET_USER_ACCOUNT on timers or route changes.


What happens if I open index.html in Chrome outside Hub?

qortalRequest is missing, so wallet auth, publish, and encrypt cannot work. Show preview mode with mock reads only. Instruct users to open via Hub/Qortal Go. Do not silently fake payments.

open outside hub
browser without qortalRequest


Use qortal://WEBSITE/MySite/gallery (service/name/path). Ensure the WEBSITE zip actually contains that path or APP-style SPA fallback will not apply—WEBSITE 404s missing files. Sync Hub chrome with QDN_RESOURCE_DISPLAYED.

website subpage link
qortal website path


Use qortal://APP/MyQApp or OPEN_NEW_TAB / LINK_TO_QDN_RESOURCE with service APP and name. For in-app routes, combine Hub render paths with pushState and optional hash fragments. Prefer Hub navigation APIs over external browsers.


What UTF-8 publish bug should I always avoid?

Raw btoa(JSON.stringify(obj)) throws or corrupts on Unicode characters. Always TextEncoder → binary string → btoa for data64 JSON publishes. This is a top production pitfall for international text.


Check typeof/object shape, required fields, schema version string, and size bounds before use. Reject unknown versions safely. Remember permissionless names: malicious publishers can reuse your identifier patterns under their own name.

validate qdn json
schema version validation


What is the difference between LIST_QDN_RESOURCES and SEARCH_QDN_RESOURCES?

LIST is suited to enumerating resources for a known name/service with richer filters in some builds. SEARCH discovers across the network with prefix options but needs minimal payloads and result normalization. Use LIST when you know the publisher; SEARCH for discovery.

list vs search qdn
LIST_QDN_RESOURCES vs SEARCH


How do identifiers work with MAIL or BLOG_POST?

Follow each ecosystem app's documented identifier schemes (Q-Mail, Q-Blog) so other clients interoperate. Prefix your own extensions carefully and never overwrite foreign coordinates. Validate payload schemas from those apps' repos.


What should I put in a JSON index resource?

Keep a versioned schema (v or schema field), lists of child identifiers, titles, timestamps, and content hashes—stay under ~25 KB. Point to FILE/IMAGE/VIDEO for heavy bytes. Publish index updates when parts change.


How do private services relate to encryption actions?

Private services (*_PRIVATE, 801, etc.) store encrypted payloads for recipients. Use ENCRYPT_DATA / DECRYPT_DATA or group encrypt/decrypt variants with the right public keys. Keep UX copy clear that decryption requires wallet approval.

private services encryption
*_PRIVATE services


No—not in shipped apps. Use relative fetch so Hub routes to the user's node, and qortalRequest for wallet flows. Hardcoded localhost breaks remote gateways, mobile, and non-default ports.

hardcoded 12391
localhost api antipattern


When Core supports it: http://localhost:12391/render/APP/<Name>?preview=true (varies by version). Still prefer Hub Preview for realistic bridge behavior. Confirm assets load under the render path.


How do I use GET_QDN_RESOURCE_STATUS before playing video?

Poll or await GET_QDN_RESOURCE_STATUS until READY (and acceptable percent loaded) before setting video src or fetching large blobs. Show progress UI; cancel cleanly on navigation away.


What parameters does PUBLISH_QDN_RESOURCE need?

At minimum: action PUBLISH_QDN_RESOURCE, owned name, service, and payload via data64 or file; identifier recommended for non-default resources. User approval required. Confirm optional metadata fields against qortal.dev for your Hub version—do not invent keys.

PUBLISH_QDN_RESOURCE params
publish required fields


How do I get a URL for an arbitrary resource path?

Prefer GET_QDN_RESOURCE_URL over string-concatenating /arbitrary/... yourself. For multi-file resources include the filepath Hub expects. Append ?attachment=true on mobile when triggering native download listeners.


OPEN_NEW_TAB opens a new Hub tab with a URL or qortal:// link. Use it for cross-app navigation while staying inside Hub. Prefer it over external browser windows for QDN targets.


Post a message like { action: 'SET_TAB', requestedHandler: 'UI', payload: { service, name, identifier?, path? } } to switch Hub tabs to a QDN resource. Feature-detect Hub support and fall back to LINK_TO_QDN_RESOURCE or OPEN_NEW_TAB.


What friends/profile list actions exist?

Hub builds may expose GET_FRIENDS_LIST, SET_PROFILE_DATA, and list item APIs—often approval-gated. Verify against your version's q-apps docs before relying on them. Store cross-user public profile data on QDN when other apps must read it.


How do I handle user cancel on approval popups?

Catch errors from qortalRequest and branch UX: user cancel should be quiet/non-alarming; technical failures can show retry. Do not loop automatic retries on cancel. Keep buttons re-enabled after cancel.

user cancel qortalRequest
approval cancelled


SEND_CHAT_MESSAGE (group) sends chat with user approval on supported Hubs. Do not use it as a reliable payment receipt channel. Confirm group context and permissions; handle cancel and rate limits.


How should category and tags be used on resources?

Set category/tags in publish metadata when supported, and read them via GET_QDN_RESOURCE_METADATA for filtering UIs. Keep tags short and consistent within your app. Do not trust tags for security decisions—validate payload bodies.


What is the APP vs WEBSITE choice for a portfolio site?

A mostly static portfolio with real 404s for bad URLs should use WEBSITE. If it is an interactive builder/marketplace with client routing, use APP. Confusing the two causes most routing bugs—read that distinction before architecture.

portfolio WEBSITE or APP
choose service type


How do I fetch a file inside a published APP zip?

FETCH_QDN_RESOURCE with service APP, name, optional identifier, and filepath set to the in-zip path (e.g. assets/config.json). Multi-file services require filepath; omitting it will not return a single nested file correctly.


What does latest publish wins mean for identifiers?

Updating the same name+service+identifier overwrites prior content for readers fetching latest. To keep history, use new identifiers or version suffixes and point the index at the current id. Design for immutability when audits matter.

immutable identifiers
version suffix identifiers


Pass prefix: true on SEARCH_QDN_RESOURCES when the identifier field is a prefix (e.g. myapp_post_). Without it, many cores expect exact identifier match. Combine with service filter and minimal payload for reliable discovery.

prefix true meaning
exact vs prefix identifier


What notifications permission actions exist?

Some Hub versions expose notifications permission actions alongside SET_TAB_NOTIFICATIONS. Feature-detect, request only from a gesture, and degrade silently if unavailable. Never depend on notifications for critical payment state.

notifications permission q-app
hub notifications


How do I debug bridge vs REST field mismatches?

Compare qortalRequest responses with the same data from relative fetch against api.qortal.org field names. Log raw payloads in Preview. Prefer bridge for wallet-bound ops and REST/fetch for anonymous public reads.


What should an LLM remember before generating Q-App code?

Do not confuse WEBSITE and APP; use qortalRequest for wallet/publish/encrypt and relative fetch for public reads; verify actions against Hub/Core; flat zip with base "./"; Connect-button auth; UTF-8 safe base64; prefix identifiers; normalize search arrays; JSON ≤25 KB; try/catch; never handle seeds.


How do I register publish rights conceptually?

Only the account that owns the registered name can publish/update resources under that name. GET_ACCOUNT_NAMES shows names you control; publishing under someone else's name fails. Plan multi-admin flows via shared wallets/groups carefully.

publish rights name owner
who can publish


What is GET_USER_WALLET vs GET_USER_ACCOUNT?

GET_USER_ACCOUNT returns address/publicKey for identity. GET_USER_WALLET / GET_USER_WALLET_INFO are approval-gated wallet details on some Hubs—use only when needed and verify fields for your version. Prefer the least-privileged action.


Call GET_PRICE (read-only) and divide integer amounts by 1e8 for human QORT. Cache briefly to avoid spamming; handle missing market data. Confirm units before using prices in SEND_COIN UX copy.


SEARCH_NAMES finds registered names (read-only). Use it for typeahead when linking to publishers. After picking a name, fetch resources with LIST/SEARCH QDN APIs and validate payloads.


How should error messages be written for users?

Map bridge errors to short actions: open in Hub, connect wallet, register a name, approve the popup, reduce payload size, or retry after publish delay. Avoid dumping raw stacks. Distinguish cancel from failure.


How do I keep Q-App builds deterministic for republish?

Pin dependency versions, rebuild clean dist/, zip contents only, and bump manifest version. After publish, verify GET_QDN_RESOURCE_PROPERTIES size/hash expectations. Stale hashed assets in an old zip cause eternal Loading.

deterministic build zip
republish clean build


What is the role of Qortal Core vs Hub for Q-Apps?

Core validates chain data, stores QDN, and exposes REST. Hub/Mobile/Extension embed the Q-App UI and inject qortalRequest for privileged actions. Your static app talks to both: fetch for public REST, qortalRequest for wallet-scoped operations.


How do I open Q-Mail or other apps for a user?

Navigate with qortal:// or LINK_TO_QDN_RESOURCE / OPEN_NEW_TAB to the target APP name used by that product. Do not scrape their private coordinates; use documented public identifiers and services. Fail soft if the app is unpublished on that node.


What packaging mistake causes /assets 404 specifically?

Using absolute /assets/... in the bundler output or HTML, or zipping a parent folder so paths disagree. Set base "./" , verify built index references ./assets/..., and zip dist contents. Check Network tab inside Hub Preview.


Pass encoding: "base64" on FETCH_QDN_RESOURCE when you need binary-safe content for files/images. Decode to Uint8Array/Blob in the app. For JSON text, you may get string/object depending on Hub—normalize before parse.


What is a safe pattern for Connect button code?

btn.onclick = async () => { try { const acc = await qr({ action: "GET_USER_ACCOUNT" }); const names = await qr({ action: "GET_ACCOUNT_NAMES", address: acc.address, limit: 10, offset: 0 }); /* enable publish if names[0] */ } catch (e) { /* show e.message */ } }; Never invoke that on DOMContentLoaded.

connect button code
GET_USER_ACCOUNT example


How do I gate publish UI without a name?

After fetching names, disable Publish and show "Register a Qortal name to publish". Do not call PUBLISH_QDN_RESOURCE hoping for a helpful error. Re-check names when the user returns from Hub name registration.


What crossorigin script issue can grey-screen an app?

Some Hub builds fail loading scripts marked crossorigin, leaving a blank/Loading UI even when files exist. If assets 200 but JS never runs, try removing crossorigin from script tags, rebuild, and re-zip.


How do I design identifier namespaces for encrypted mail-like apps?

Use public JSON indexes under myapp_pub_* and ciphertext under myapp_priv_* (or private services). ENCRYPT_DATA with recipient publicKeys; DECRYPT_DATA with counterparty publicKey. Never put secrets in public JSON.

encrypted mail identifiers
public vs private namespace


GET_GROUPS_WITH_MEMBER lists groups that include a member address (read-oriented; confirm approval needs for your version). Use it to personalize group UIs after Connect. Pair with LIST_GROUPS for broader discovery.


Ensure pollName uniqueness, pass pollOptions as an array of option objects, confirm copy before CREATE_POLL/VOTE_ON_POLL approvals, and handle cancel. Do not encode critical funds movement only in poll text—use SEND_COIN for payments.


What offline-safe rules apply to Q-App assets?

Ship all critical JS/CSS/fonts/images in the zip; use relative URLs; avoid CDN and runtime code downloads. QDN itself may still need network to fetch remote resources, but the app shell should render offline once cached by Hub.


How do I report resource display to Hub for APP routes?

Use QDN_RESOURCE_DISPLAYED with service APP, name, and path when your in-app route should appear in Hub chrome/copy-link. Keep path aligned with what you would put in a qortal:// link.

QDN_RESOURCE_DISPLAYED APP
app route copy link


What is the teaching-default artifact for small tools?

Default to a single-file HTML Q-App for small tools, and Vite + flat zip for production SPAs. Single-file simplifies packaging (still may zip one html); SPAs need careful relative asset paths and SPA service APP.


How do I verify action names before shipping?

Check https://qortal.dev/docs/q-apps and Core q-apps.js for the user's Hub/Core version. Feature-detect optional actions. Do not invent parameters from memory—wrong keys can fail publishes or empty searches.

verify actions before ship
do not invent parameters


TRANSFER_ASSET moves assets with user approval (confirm params on target Hub). Explain asset name/amount in UI first. Like SEND_COIN, treat confirmation and any later notify/publish as separate steps.


What METADATA fields are commonly useful in galleries?

Title, tags, category from GET_QDN_RESOURCE_METADATA plus filename/mimeType/size from PROPERTIES and a THUMBNAIL URL. Lazy-load full FETCH only on open. Validate strings before rendering into HTML.

gallery metadata fields
lazy load resource


How do I avoid XSS when rendering Markdown from DOCUMENT?

Fetch DOCUMENT as text, run a sanitizer or render to text-only, never inject raw HTML. Combine with CSP-friendly in-zip scripts only. Treat every publisher as untrusted even if the name looks familiar.

markdown xss document
render document safe


What is the difference between gateway context and full Hub?

window._qdnContext may be "gateway" vs fuller Hub environments. Some privileged behaviors differ; always feature-detect qortalRequest and degrade. Theme still via _qdnTheme. Test both if you support public gateways.


How do I publish multiple related resources with fewer popups?

Feature-detect PUBLISH_MULTIPLE_QDN_RESOURCES to batch JSON metadata + THUMBNAIL + FILE in one approval when supported. Otherwise sequence publishes with a progress UI after one user click. Explain what will be published before starting.

batch related publishes
fewer publish popups


What FILE size strategy should apps use?

Prefer one FILE when under comfortable Hub timeouts; for larger binaries, chunk into parts with a JSON index and bounded parallel fetches. Probe STATUS before heavy FETCH. Show clear size warnings before user-approved publish.


How do I integrate Hub back buttons with my SPA?

Listen to popstate for in-app routes and integrate NAVIGATION_HISTORY when available. Ensure each pushState has a matching UI state. Do not break Hub-level back by replacing history incorrectly.

hub back button spa
popstate integration


What should be in a Q-App README for other developers?

Document identifier prefixes, JSON schema versions, services used, publish rights (name ownership), Hub version tested, and example qortal:// links. Point to qortal.dev for actions. Clear conventions beat clever undocumented namespaces.

q-app readme conventions
document identifiers


How do I fetch account names with pagination?

Pass limit and offset to GET_ACCOUNT_NAMES and loop until a short page returns. Most users have few names—still code defensively. Cache after Connect for the session unless the user refreshes names.

GET_ACCOUNT_NAMES pagination
limit offset names


What is a good identifier for per-user app config?

Use a stable id like myapp_config_v1 under the user's name and service JSON. Latest publish wins for updates. Bump _v2 only for breaking schema changes and migrate readers carefully.


Request GET_QDN_RESOURCE_URL for service THUMBNAIL, name = user name, identifier qortal_avatar, and set img.src. On failure show a placeholder. Do not block Connect UX on avatar load.


CREATE_POLL rejects or mis-handles pollOptions when passed as a string. Pass an array of option objects (e.g. { optionName }). Validate non-empty unique labels client-side before the approval popup.

pollOptions must be array
CREATE_POLL options string bug


How do relative asset paths interact with deep links?

Deep links into /render/APP/Name/some/route still need assets resolved from the app root, not the deep path. Relative ./assets from a nested route can break—use root-correct relative URLs from the bundler base or Hub trailing-slash strategies.

deep link assets
nested route asset paths


What is the recommended concurrency for chunk fetches?

Use a small pool (about 3–6 concurrent FETCH_QDN_RESOURCE calls), retry transient failures with backoff, and update progress from STATUS. Hundreds in Promise.all cause timeouts and UI jank.

chunk fetch concurrency
parallel fetch limit


DOCUMENT suits larger text/JSON documents; FILE is generic binary/download oriented. If clients should edit structured text in-app, DOCUMENT (or JSON when tiny) is clearer; if users download opaque blobs, FILE fits. Either way validate size and mime.


What Hub UI actions help multi-page sites?

QDN_RESOURCE_DISPLAYED keeps copy-link aligned; OPEN_NEW_TAB/LINK_TO_QDN_RESOURCE opens peers; SET_TAB switches tabs; SET_TAB_NOTIFICATIONS badges unread. Combine with real WEBSITE file paths or APP SPA fallback appropriately.

hub ui actions website
multi-page hub integration


How do I safely mock data in preview mode?

When the bridge is missing, serve local fixture JSON for reads and disable Publish/Pay buttons. Label the UI Preview. Never mark a mock payment as success. The same code paths should call qr() in Hub.


What causes stale hashed files after rebuild?

Zipping an old dist/ or mixing previous index-*.js hashes with a new index.html yields Loading forever. Always clean build, zip fresh contents, and Preview once before publish. Clear Hub cache if it pins old assets during testing.


Typical shape: { address, publicKey }. Use address for GET_ACCOUNT_NAMES and publicKey for encryption counterparties when appropriate. Do not log sensitive wallet details verbosely in production UIs.

GET_USER_ACCOUNT fields
address publicKey


SEARCH_CHAT_MESSAGES searches chat on supported Hubs and may be approval-gated depending on version. Confirm parameters in qortal.dev. Do not treat chat search as a transactional ledger.


What is the end-to-end path for a WEBSITE visitor link?

Publisher builds static files → Hub publishes service WEBSITE under owned name → visitors open qortal://WEBSITE/<Name>/path. Missing files 404. For interactive wallet features on every page, consider APP instead.


How do I combine JSON metadata with VIDEO content?

Publish VIDEO under myapp_video_<id>, THUMBNAIL under myapp_video_<id>_thumb, and JSON myapp_video_<id>_meta with title/tags/pointers. Search prefix myapp_video_ for discovery; fetch metadata first, then STATUS, then play.

video metadata pattern
json pointer to video


What REST mode mistake causes duplicate resources in UI?

Using mode=ALL when you meant LATEST lists historical publishes as separate rows. Prefer LATEST for current catalogs, or dedupe by name+service+identifier yourself if you need ALL for history UIs.

duplicate resources search mode
ALL vs LATEST duplicates


How do I implement bounded concurrency in JS?

Keep a queue of identifiers and N workers: while queue length, worker shifts an id, await FETCH_QDN_RESOURCE, push result, repeat. Avoid awaiting Promise.all on the entire list. Surface partial errors per id.

worker pool fetch
bounded concurrency code


What should happen when parent.qortalRequest is only a stub?

If parent === top and the stub cannot reach Hub, wallet calls fail—tell the user to open the app from Hub or a real Hub tab. Implement a message relay only when you control the parent embedder.


Call SAVE_FILE with blob and filename from a user gesture on mobile when anchor download fails. Ensure blob type/filename are set. Feature-detect and fall back to GET_QDN_RESOURCE_URL with attachment=true when appropriate.


What is the relationship between registered names and resources?

Resources publish under a name you own; that name is part of the public coordinate others use to fetch. Changing ownership/name registration affects who can update. Design UX around primary name selection when users have several names.

names and resources
primary name selection


How do I avoid inventing qortalRequest parameters?

Copy parameter names from qortal.dev/docs/q-apps or Core sources for the installed version. When unsure, feature-detect and keep payloads minimal—especially for SEARCH_*. Extra unknown keys can break searches.


What packaging checklist prevents grey screens?

index.html at zip root; flat zip of dist contents; base "./"; relative ./assets; no required CDN; rebuild after config changes; Preview Network 200s; consider trailing slash; remove problematic crossorigin; case-sensitive paths.

grey screen checklist
packaging checklist


How do inter-app SEARCH prefixes enable ecosystems?

Agree on myapp_ prefixes and schema versions; publish discovery JSON; partners SEARCH_QDN_RESOURCES with prefix: true; open hits via LINK_TO_QDN_RESOURCE. Validate every foreign payload—prefixes are not authentication.

ecosystem search prefixes
partner app discovery


How do I handle ENCRYPT_DATA for groups?

Pass a publicKeys array to ENCRYPT_DATA for group encryption, or use ENCRYPT_QORTAL_GROUP_DATA / DECRYPT_QORTAL_GROUP_DATA for group-scoped flows. Explain recipients before approval. Store ciphertext under private namespaces.


What UI pattern fits Hub-embedded Q-Apps?

One primary Connect CTA, clear publish/pay confirmations, theme via _qdnTheme, large tap targets, and progressive loading with STATUS for media. Avoid assuming desktop hover. Keep critical actions gesture-gated.

hub embedded ui pattern
q-app ux pattern


How do I test publish then search discovery?

Publish JSON with prefixed identifier, wait/retry, SEARCH_QDN_RESOURCES with prefix: true and minimal payload, normalize to array, and assert your item appears. If empty, strip optional fields and confirm service spelling.

test publish search
discovery after publish