Block Marketplace SDK: defineMarketplaceBlock + sandbox security model
The SDK is the contract that lets third-party blocks run safely on tens of thousands of organization sites. Import from src/lib/marketplaceBlockSDK.ts (or copy the type shape into your own build).
Hello-world example:
import { defineMarketplaceBlock } from '@turtini/block-sdk'
export default defineMarketplaceBlock({
type: 'pixel-studios.testimonial',
label: 'Testimonial',
icon: 'M4 6h16',
category: 'Marketing',
capabilities: ['fetch.public'],
description: 'Customer testimonial with photo + quote.',
fields: [
{ key: 'quote', label: 'Quote', kind: 'longtext', required: true },
{ key: 'name', label: 'Customer name', kind: 'text', required: true },
{ key: 'photo', label: 'Photo URL', kind: 'image' },
],
render: ({ data }) => `
<figure style="padding:2rem;background:#f8fafc;border-radius:1rem;text-align:center">
<img src="${data.photo}" style="width:80px;height:80px;border-radius:50%;object-fit:cover">
<blockquote style="margin:1rem 0;font-size:1.1rem">${escape(data.quote)}</blockquote>
<figcaption style="color:#64748b">— ${escape(data.name)}</figcaption>
</figure>
`,
})
Sandbox security model:
Every marketplace block runs in an iframe with sandbox="allow-scripts" and NO allow-same-origin. Origin is opaque/null. That means:
• The block CANNOT read parent document cookies, localStorage, or DOM
• The block CANNOT touch Firebase auth tokens on the parent
• The block CANNOT exfiltrate via a fetch that piggybacks on parent credentials
• The block CAN postMessage back into the host to request capability-gated work (fetch, lead submit, checkout, analytics)
Capabilities:
Every outgoing request is validated by the host against the listing's published capability set. Anything outside that list is rejected before it hits the network.
• fetch.public — call public Turtini endpoints (live stock, restaurant wait, etc.)
• fetch.org-readonly — call org-scoped APIs the installing org has granted
• storage.media — load images/video from the platform CDN
• submit.lead — POST a lead/submission back to the installing org
• submit.purchase — open a Stripe checkout against the installing org's Connect account
• analytics.track — emit a custom analytics event
SDK versioning:
The current SDK is 0.1.0. Reviewers reject bundles whose declared sdkVersion is newer than the host's. Bump patch for non-breaking host additions, minor for new opt-in capabilities, major for breaking changes.
What render() should NOT do:
• Don't try to mount React/Vue/Svelte. The render context is HTML-only — returning a string is the contract. Any JS you embed inside the HTML runs inside the iframe, but you don't get access to the broader Turtini React tree.
• Don't fetch credentials directly from the parent (you can't anyway; you'd just get cross-origin errors).
• Don't write to localStorage expecting persistence — sandboxed origin gets a fresh storage every page load.