Mastodon feed for your website

Unlike most social media platforms, displaying your latest mastodon posts is a doddle with vanilla JavaScript

Demo

screenshot of 30 odd "apple-touch-startup-image" image tags for the head

@jensimmons hi Jen! enjoyed your appearance on shop talk. you said to reach out if devs have safari q’s, so!

could you furnish us with an up-to-date list of splash screen image sizes for pwas? I tried using github.com/elegantapp/pwa-asse but alas it’s not showing an image for my iphone 15

seems like a lot of overhead also. thanks!

@kelsonv thank you so much! 🖤

1. Get your Mastodon ID

First you’ll need your Mastodon ID

https://example.social/api/v1/accounts/lookup?acct=your-username

In our case https://mastodon.cloud/api/v1/accounts/lookup?acct=blackspike returns the ID 109461933672061521

2. Fetch the posts

Then you’ll need to make a simple fetch call

const postsUrl = "https://mastodon.cloud/api/v1/accounts/109461933672061521/statuses"

const postsFetch = await fetch(postsUrl)

const posts = await fetchPosts.json()
That will return a JSON array like this
[
  {
    "id": "110299745197176137",
    "created_at": "2023-05-02T15:15:45.611Z",
    "in_reply_to_id": null,
    "in_reply_to_account_id": null,
    "sensitive": false,
    "spoiler_text": "",
    "visibility": "public",
    "language": "en",
    "uri": "https://mastodon.cloud/users/blackspike/statuses/110299745197176137",
    "url": "https://mastodon.cloud/@blackspike/110299745197176137",
    "replies_count": 0,
    "reblogs_count": 0,
    "favourites_count": 0,
    "edited_at": null,
    "favourited": false,
    "reblogged": false,
    "muted": false,
    "bookmarked": false,
    "pinned": true,
    "content": "\u003cp\u003eWe are delighted to push our new website live – we welcome shares \u0026amp; feedback 🖤\u003c/p\u003e\u003cp\u003e\u003ca href=\"https://www.blackspike.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003eblackspike.com\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e",
    "filtered": [],
    "reblog": null,
    "application": null,
    "account": {
      "id": "109461933672061521",
      "username": "blackspike",
      "acct": "blackspike",
      "display_name": "blackspike design",
      "locked": false,
      "bot": false,
      "discoverable": true,
      "group": false,
      "created_at": "2022-12-05T00:00:00.000Z",
      "note": "\u003cp\u003eA freelance full-stack creative development team working remotely from Brighton, UK\u003c/p\u003e",
      "url": "https://mastodon.cloud/@blackspike",
      "avatar": "https://media.mastodon.cloud/accounts/avatars/109/461/933/672/061/521/original/eb1f3ebaecf23fce.png",
      "avatar_static": "https://media.mastodon.cloud/accounts/avatars/109/461/933/672/061/521/original/eb1f3ebaecf23fce.png",
      "header": "https://media.mastodon.cloud/accounts/headers/109/461/933/672/061/521/original/53e72b9acfdbc9e7.jpg",
      "header_static": "https://media.mastodon.cloud/accounts/headers/109/461/933/672/061/521/original/53e72b9acfdbc9e7.jpg",
      "followers_count": 5,
      "following_count": 4,
      "statuses_count": 16,
      "last_status_at": "2023-05-02",
      "noindex": false,
      "emojis": [],
      "roles": [],
      "fields": [
        {
          "name": "www",
          "value": "\u003ca href=\"https://blackspike.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003eblackspike.com\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e",
          "verified_at": "2023-04-13T12:30:23.994+00:00"
        },
        {
          "name": "Twitter",
          "value": "\u003ca href=\"https://twitter.com/blackspikeltd\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003etwitter.com/blackspikeltd\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e",
          "verified_at": null
        },
        {
          "name": "new website coming soon!",
          "value": "\u003ca href=\"https://beta.blackspike.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003ebeta.blackspike.com\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e",
          "verified_at": "2023-04-13T12:30:51.474+00:00"
        }
      ]
    },
    "media_attachments": [
      {
        "id": "110299741526912479",
        "type": "gifv",
        "url": "https://media.mastodon.cloud/media_attachments/files/110/299/741/526/912/479/original/492f846154047751.mp4",
        "preview_url": "https://media.mastodon.cloud/media_attachments/files/110/299/741/526/912/479/small/492f846154047751.png",
        "remote_url": null,
        "preview_remote_url": null,
        "text_url": null,
        "meta": {
          "original": {
            "width": 960,
            "height": 960,
            "frame_rate": "60/1",
            "duration": 14.167,
            "bitrate": 6587363
          },
          "small": {
            "width": 400,
            "height": 400,
            "size": "400x400",
            "aspect": 1.0
          },
          "focus": { "x": 0.0, "y": 0.0 }
        },
        "description": "a screen recording of the homepage of blackspike.com being scrolled",
        "blurhash": "U58WaQpvI.$+{[EkNds+8zE0x]kDF-xct7Sc"
      }
    ],
    "mentions": [],
    "tags": [],
    "emojis": [],
    "card": {
      "url": "https://www.blackspike.com/",
      "title": "blackspike design - creative web development",
      "description": "blackspike design is a freelance full-stack creative development team working remotely from Brighton, UK",
      "type": "link",
      "author_name": "",
      "author_url": "",
      "provider_name": "",
      "provider_url": "",
      "html": "",
      "width": 400,
      "height": 210,
      "image": "https://media.mastodon.cloud/cache/preview_cards/images/026/387/228/original/f69a0a79d5a5f7ec.jpg",
      "embed_url": "",
      "blurhash": "UGBeH^}Z=eA?[Ca0nOK3-BS#$is.=Lf,,?w^"
    },
    "poll": null
  }
]

You can see in that example post there is a video and thumbnail image in the media_attachments that we use in our demo carousel.

3. Create HTML from the returned JSON

Use a template literal to create the HTML list items and format the date with toLocaleDateString

const postsList = document.querySelector('.posts')

const postsUrl = 'https://mastodon.cloud/api/v1/accounts/109461933672061521/statuses'

const getPosts = async () => {

  const fetchPosts = await fetch(postsUrl)

  const posts = await fetchPosts.json()

  const dateOptions = { year: 'numeric', month: 'long', day: 'numeric' }

  const markup = posts.map(toot => `
      <li class='toot'>
        <div class='content'>${toot.content}</div>
        <footer>
            ${new Date(toot.created_at).toLocaleDateString('en-GB', dateOptions)}
        </footer>
      </li>
      `
    )
    .join('')

  postsList.innerHTML = markup
}

getPosts()

Add a dash of CSS and you’re done

Of course you can add links, show images, play videos and hide replies etc, but it’s fantastic that you don’t have to worry about OAUTH or API keys or paying billionaires for the privilege of displaying your own content 🤌

See the Pen latest toots! by blackspike (@blackspike) on CodePen.


If you need help with adding a custom feed to your site, do get in touch!