A near-zero-dependency, vanilla JS/CSS embed toolkit for Bluesky / AT Protocol content.
You load one script (dist/embed.js) and it auto-loads only what is needed for posts/discussions, profile cards, and members grids.
Disclaimer: Entirely vibe coded.
- Single entrypoint: one
<script>tag (dist/embed.js) as loader. - Auto runtime loading: loads
post.js,profile.js, andmembers.jsonly when matching containers exist. - Post + discussion embeds from AT URI or Bluesky-style post URLs.
- Profile widget with optional avatar, cover, metrics, and CTA buttons.
- Members widget for list URLs, list AT URIs, starter-pack URLs, and starter-pack AT URIs.
Load the loader:
<script src="https://cdn.jsdelivr.net/npm/atproto-embed@latest/dist/embed.js"></script><div
class="atproto-embed"
data-uri="at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mhgprzgahs2l"
data-mode="post"
data-width="100%"
data-max-width="728px"
data-show-likes="true"
data-show-reposts="true"
data-show-replies="true"
data-show-quotes="true"
data-show-bookmarks="true"
data-show-metrics="true"
data-show-timestamp="true"
data-show-actions="true"
data-show-reply-context="true"
data-show-embeds="true"
data-show-images="true"
data-show-video="true"
data-show-external="true"
data-show-quote-posts="true"
data-external-layout="vertical"
data-show-badges="true"
data-show-labels="true"
>
</div><div
class="atproto-embed"
data-uri="at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mhgprzgahs2l"
data-mode="discussion"
data-width="100%"
data-max-width="728px"
data-show-likes="true"
data-show-reposts="true"
data-show-replies="true"
data-show-quotes="true"
data-show-bookmarks="true"
data-show-metrics="true"
data-show-timestamp="true"
data-show-actions="true"
data-show-reply-context="true"
data-show-embeds="true"
data-show-images="true"
data-show-video="true"
data-show-external="true"
data-show-quote-posts="true"
data-external-layout="vertical"
data-show-badges="true"
data-show-labels="true"
data-show-reply-quote-labels="false"
data-show-main-post="true"
data-show-liked-by="true"
data-show-replies-tab="true"
data-show-quotes-tab="true"
data-show-tabs="true"
data-show-sort="true"
data-show-join-button="true"
data-replies-sort="oldest"
>
</div><div
class="atproto-profile"
data-profile="bsky.app"
data-width="100%"
data-max-width="440px"
data-size="full"
data-show-avatar="true"
data-show-display-name="true"
data-show-handle="true"
data-show-verification="true"
data-show-description="true"
data-show-cover="true"
data-show-metrics="true"
data-show-followers="true"
data-show-following="true"
data-show-posts="true"
data-show-follow="true"
data-show-connect="true"
data-client-name="Bluesky"
data-client-domain="bsky.app"
>
</div><div
class="atproto-members"
data-list="https://bsky.app/profile/bsky.app/lists/3lvpca43j5z26"
data-width="100%"
data-max-width="960px"
data-columns="3"
data-limit="15"
data-show-avatar="true"
data-show-display-name="true"
data-show-handle="true"
data-show-verification="true"
data-show-metrics="true"
data-show-posts="true"
data-show-followers="true"
data-show-following="true"
>
</div>at://did:.../app.bsky.feed.post/<rkey>https://<client>/profile/<handle-or-did>/post/<rkey>
- List AT URI:
at://did:.../app.bsky.graph.list/<rkey> - List URL:
https://<client>/profile/<handle-or-did>/lists/<rkey> - Starter-pack AT URI:
at://did:.../app.bsky.graph.starterpack/<rkey> - Starter-pack URL (profile form):
https://<client>/profile/<handle-or-did>/starter-pack/<rkey>
Starter-pack inputs are resolved through app.bsky.graph.getStarterPack and then rendered via its backing list.
Build output:
dist/embed.js: lightweight loader entrypointdist/post.js: post + discussion runtimedist/profile.js: profile runtimedist/members.js: members runtimedist/post.css,dist/profile.css,dist/members.css: standalone CSS copiesdist/embed.css: standalone CSS copy
Run a static server:
npx -y serve -l 1234 .Build distribution bundles:
npm run buildRelease flow (build + version + tag + push):
npm run release