What is Infa
Infa Chrome Extension
How to use and install our Chrome Extension
Community
Browse community design systems
Release Notes
Featuring the latest changes to keep you up-to-date
Research Program
Participate in shaping the future of Infa
SOC 2 Type II Compliance Badge
Enterprise Ready
SOC 2 Type II certified for enterprise-level security and compliance
EventsLearnPricing
Sign up for free

Feedback

Share your feedback

Close
Anonymous submissions are auto-posted to #feedback channel. You can also share directly in Slack.

Need help?

Reach out to contact@infa.ai or see docs

Navigation
Search...
⌘ K
Focus sentinel
Close

Search Documentation

PressESCto close
Focus sentinel
Infa Documentation
  • General Information
    • Boards
    • Components
      • Main Components Vs Component Views
      • Component Tagging
      • Component Anatomy
    • Labels
    • Teams
    • Authentication
    • Permissions
      • Inviting People to Teams
      • Inviting People to Boards
    • Billing & Subscription
      • Managing Subscription
      • Promo Code Activation
    • Data Import, Export & Sync
      • Local vs. Cloud Boards
  • App Blocks
    • Building App Blocks
  • Chrome Extension
    • Deep Links
    • Using Labels
    • Updating the Extension
  • Integrations
    • MCP Server
    • Claude Code Plugin
    • External API
    • Progressive Web App
    • Figma Plugin
    • Coda Pack
    • Overview
    • Managing Runs
    • Board Agents
    • Overview
    • Infa Capture
    • Installation
    • Authentication
    • Connect a Board
    • Run Modes
    • Hooks & Agents
    • File Sync
    • CLI Reference
Switch to Light theme
Switch to Dark theme
TermsPrivacyChat with Us
  1. Docs
  2. App Blocks

Building App Blocks

Build your own App Block with an AI agent over MCP — the sandbox model, the infa:* postMessage protocol, the connector bridge for live integration data, and the publish lifecycle.

Anyone can build an App Block: it is one HTML file plus a handful of MCP tool calls. The fastest way is to hand the work to an AI agent — Infa gives you a ready-to-paste prompt, and the MCP server exposes the full lifecycle from register to publish. This page documents the same contract the server hands to agents, so you can also build a block by hand.

Copy link
Build with an AI Agent

The quickest path is to copy Infa's ready-made agent prompt, which includes connection steps, this contract, and your board ID:

  • In the App Block picker (insert menu → App Block → Community tab), the Build your own block panel has a Copy prompt for your agent button. The On this board tab has the same action in its footer.
  • Or copy it from infa.ai/profile?tab=blocks.

Paste the prompt into Claude Code, Cursor, or any MCP-enabled agent and describe what the block should do.

To set an agent up manually instead:

  1. Connect it to the Infa MCP server (HTTP transport, OAuth login on first call — no API key):

    Claude Code
    Copy code
    Copy code
    claude mcp add --transport http Infa https://infa.ai/api/mcp

    Other MCP clients: add an HTTP server with the URL https://infa.ai/api/mcp. For stdio-only clients, use npx -y mcp-remote https://infa.ai/api/mcp. See MCP Server for details.

  2. Have the agent call the infa_appBlockGuide tool. It returns the server's current version of the contract below — always fetch it before building or updating a block.

Copy link
The Sandbox Model

A block is a single, self-contained HTML document — inline CSS and JavaScript, no external scripts, stylesheets, or CDNs. Infa renders it in a sandboxed iframe with allow-scripts only: the block has an opaque origin and no direct network access. It communicates with the host page exclusively via postMessage.

Copy link
Host Protocol (postMessage)

Messages your block receives (listen for message events on window):

MessageShapeMeaning
infa:props{ type: "infa:props", props }The block's runtime props. Sent on load and again whenever the user edits props — re-render on every message.
infa:context{ type: "infa:context", context: { surface, blockId, hints, version } }Where the block is rendered; surface is "document" or "canvas".
infa:storage{ type: "infa:storage", storage }Previously persisted state, when any exists.

Messages your block sends (via window.parent.postMessage(message, "*")):

MessageShapeMeaning
infa:resize{ type: "infa:resize", height }Request an iframe height in px. Send after every render so the host fits your content.
infa:storage-update{ type: "infa:storage-update", detail: { key, value } }Persist one storage key — or send detail: { replace: { ... } } to replace all storage.

A minimal skeleton that handles props, escaping, and resize:

block.html
Copy code
Copy code
<!doctype html>
<style>
:root { color-scheme: light dark; }
body { font-family: system-ui, sans-serif; margin: 0; padding: 12px; }
</style>
<div id="root"></div>
<script>
const root = document.getElementById("root");
function render(props) {
// textContent escapes untrusted data — props are user-supplied
root.textContent = props.label ?? "Set a label via Edit props.";
parent.postMessage(
{ type: "infa:resize", height: document.body.scrollHeight },
"*"
);
}
window.addEventListener("message", (event) => {
if (event.data?.type === "infa:props") render(event.data.props ?? {});
});
</script>

Copy link
Live Integration Data (the Connector Bridge)

Blocks cannot fetch directly — provider data flows through the host, which proxies each request to the provider's official API with the viewing user's session. Tokens never reach the block.

Send a request:

connector request
Copy code
Copy code
window.parent.postMessage(
{
type: "infa:connector-request",
requestId: "req-1", // any unique string, echoed back in the response
connectorId: props.connectorId, // always from a prop — never hardcoded
request: {
path: "/repos/owner/repo/pulls?state=open",
method: "GET", // optional; GET unless the provider needs POST
body: undefined, // optional JSON body for POST requests
},
},
"*"
);

The host replies with one of:

  • { type: "infa:connector-response", requestId, ok: true, data } on success
  • { type: "infa:connector-response", requestId, ok: false, error } on failure

Supported provider APIs: GitHub, Figma, and Slack (GET); Notion (GET and POST); Linear (POST /graphql). Requests outside the provider's official API are rejected.

Copy link
Connector Rules

Never hardcode a connectorId or credential. Take the connection as a prop, and declare that prop in inputSchema with "format": "infa-connector-id" — Infa renders it as a dropdown of the board's connections in the props editor:

inputSchema excerpt
Copy code
Copy code
{
"type": "object",
"properties": {
"connectorId": {
"type": "string",
"title": "GitHub connection",
"format": "infa-connector-id"
}
},
"required": ["connectorId"]
}

Declare the providers you use in contextHints.connections (provider slugs, e.g. ["github"]) so listings badge the block as needing a connection.

Copy link
Lifecycle (Infa MCP Tools)

StepTool
Fetch this contractinfa_appBlockGuide — returns the server's current version of this guide; call it before building or updating
Registerinfa_registerAppBlock { boardId, name, description, icon, category, sourceHtml, inputSchema?, defaultProps?, contextHints? } — returns a blockTypeId and auto-installs the block on the board
Previewinfa_appBlockPreview — render-check the block before and after changes
Iterateinfa_updateAppBlock { blockTypeId, ...changedFields } — update in place; never register duplicates
Insertinfa_insertAppBlock — embed the block into a document or canvas paper
List connectionsinfa_listConnections { boardId } — the board's integration connections; users bind one to your block through the props editor
Publishinfa_publishAppBlock { blockTypeId, visibility: "published" } — share the block with the community; "board" retracts it
warning icon
Updates apply immediately
There is no version pinning yet — every existing install of a block renders the new sourceHtml the moment you update it, including installs on other users' boards if the block is published. Always preview before updating a widely-installed block.

Owners manage published blocks at infa.ai/profile?tab=blocks.

Copy link
Quality Bar

Before publishing to the community, make sure your block clears the same bar Infa's own blocks do:

  • Escape all data you interpolate into HTML (use textContent or an esc() helper) — props and API responses are untrusted.
  • Support dark mode: style with CSS custom properties plus @media (prefers-color-scheme: dark).
  • Handle the empty state (no props bound yet) with a helpful placeholder that tells the user what to configure.
  • Validate props and list required ones in inputSchema.required.
  • Never embed credentials, tokens, or personal data in sourceHtml — published blocks are public.

Copy link
Next Steps

App Blocks Overview
Using, configuring, and publishing blocks on your boards
MCP Server
Connect Claude Code, Cursor, or Claude Desktop to Infa

Content Feedback

Feedback on selected content

Close
Anonymous submissions are auto-posted to #feedback channel. You can also share directly in Slack.

Need help?

Reach out to contact@infa.ai or see docs

PreviousLocal vs. Cloud Boards
NextDeep Links