---
title: Guidelines and best practices
description: Things to keep in mind when implementing a Mastodon app.
menu:
  docs:
    weight: 10
    parent: api
---

## Login {#login}

**The user must be able to log in to any Mastodon server from the app**. This means you must ask for the server's domain and use the app registrations API to dynamically obtain OAuth2 credentials.

{{< page-ref page="client/authorized" >}}

{{< page-relref ref="methods/oauth" caption="OAuth methods" >}}

{{< page-relref ref="api/oauth-scopes" caption="OAuth scopes" >}}

## Usernames {#username}

**Decentralization must be transparent to the user**. It should be possible to see that a given user is from another server, for example, by displaying their `acct` somewhere. Note that `acct` is equal to `username` for local users and equal to `username@domain` for remote users.

## Handling IDs within API responses {#id}

**Always treat IDs as opaque strings**.

Vanilla Mastodon entity IDs are generated as integers (usually using a variant of [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID), although older IDs or IDs for internal use only may be auto-incrementing integers), and the API response serializer will cast them to string. However, this does not mean that IDs _are_ integers, nor should they be cast to integers! Doing so can lead to broken client apps due to integer overflow or other errors related to casting.

Unofficial implementations of the Mastodon API are not guaranteed to use numeric lexical forms or even numbers at all. For example, an ID generation algorithm like [Flake](https://github.com/boundary/flake) will produce 128-bit integers encoded using a base-62 alphabet, so such IDs will contain letters. As another example, an ID generation algorithm that uses HTTPS URIs does not represent a number at all.

With that said, because vanilla Mastodon uses IDs that are string representations of numbers, they can still be sorted chronologically by doing the following:

1. Sort by length. Newer statuses will have longer IDs.
2. Sort lexically. Newer statuses will have at least one digit that is higher when compared positionally.

Clients generally do not need to sort IDs; the server is responsible for sorting IDs before generating the API response. Mastodon will generally sort API responses in reverse chronological order by time of insertion into its database, although some API responses may be sorted by relevance (such as search results or trends).

## Paginating through API responses {#pagination}

Many API methods allow you to paginate for more information, using parameters such as `limit`, `max_id`, `min_id`, and `since_id`.

limit
: The maximum number of results to return. Usually, there is a default limit and a maximum limit; these will vary according to the API method.

max_id
: String. All results returned will be lesser than this ID. In effect, sets an upper bound on results.

since_id
: String. All results returned will be greater than this ID. In effect, sets a lower bound on results.

min_id
: String. Returns results immediately greater than this ID. In effect, sets a cursor at this ID and paginates forward. (Available since v2.6.0.)

For example, a client might fetch `https://mastodon.example/api/v1/accounts/1/statuses` with certain parameters, and Mastodon will return the following results in the following cases:

- Setting `?max_id=1` will return no statuses since there are no statuses with an ID earlier than `1`.
- Setting `?since_id=1` will return the latest statuses since there have been many statuses since `1`.
- Setting `?min_id=1` will return the oldest statuses, as `min_id` sets the cursor at `1` and paginates forward.

Some API methods operate on entity IDs that are not publicly exposed in the API response and are only known to the backend and the database. (This is usually the case for entities that reference other entities, such as Follow entities which reference Accounts, or Favourite entities which reference Statuses, etc.) To get around this, Mastodon may return links to a "next" and/or "previous" page with prepopulated values for these parameters. Consider the following fictitious API call and response:

```http
GET /api/v1/endpoint HTTP/1.1
Host: mastodon.example
Authorization: Bearer <access_token>
```
```http
HTTP/1.1 200 OK
Link: <https://mastodon.example/api/v1/endpoint?max_id=7163058>; rel="next", <https://mastodon.example/api/v1/endpoint?min_id=7275607>; rel="prev"

[
  {
    "//": "some Entity"
  },
  "more Entities in an Array"
]
```

In this case, you can retrieve the HTTP `Link` header and parse it for links to the next or previous page. See [RFC 9651](https://www.rfc-editor.org/rfc/rfc9651) for parsing HTTP headers, and see [RFC 8288](https://datatracker.ietf.org/doc/rfc8288/) for information specifically about the `Link` header. Following the `next` link relation should show you the next page. Following the `prev` link relation should show you the previous page.

## Deprecations {#deprecations}

Mastodon rarely removes APIs, but that can still happen from time to time. Therefore, it is recommended to keep up with Mastodon releases and keep an eye out for deprecated APIs.

Furthermore, to help implementers with spotting use of deprecated APIs, Mastodon 4.4.0 makes use of the `Deprecation` header defined in [RFC9745](https://datatracker.ietf.org/doc/html/rfc9745). It is recommended that library and application developers look for this header and display warnings in their development environments so they can spot these deprecated APIs before they get retired.

## Formatting {#formatting}

Plain text is not available for content from remote servers, and plain text syntax rules may vary wildly between Mastodon and other fediverse applications. For certain attributes, such as the content of statuses, **Mastodon provides sanitized HTML**. See [HTML Sanitization]({{< relref "spec/activitypub#sanitization" >}}) for more details. You may expect these tags to appear in the content:

* `<p>`
* `<br>`
* `<span>`
* `<a>`

{{< page-relref ref="spec/activitypub#sanitization" caption="ActivityPub > HTML Sanitization" >}}

### Mentions, hashtags, and custom emoji {#tags}

Mentions and hashtags are `<a>` tags. Custom emojis remain in their plain text shortcode form. To give those entities their semantic meaning and add special handling, such as opening a mentioned profile within your app instead of as a web page, metadata is included with the [Status]({{< relref "entities/Status" >}}), which can be matched to a particular tag.

{{< page-relref ref="entities/Status" caption="Status entity" >}}

{{< page-relref ref="entities/Status#mentions" caption="Status#mentions" >}}

{{< page-relref ref="entities/Status#tags" caption="Status#tags" >}}

{{< page-relref ref="entities/Status#emojis" caption="Status#emojis" >}}

### Link shortening {#links}

Links in Mastodon are not shortened using URL shorteners, and the usage of URL shorteners is heavily discouraged. URLs in text always count for 23 characters and are intended to be shortened visually. For that purpose, a link is marked up like this:

```html
<a href="https://example.com/page/that/is/very/long">
  <span class="invisible">https://</span>
  <span class="ellipsis">example.com/page</span>
  <span class="invisible">/that/is/very/long</span>
</a>
```

The spans with the `invisible` class can be hidden. The middle span is intended to remain visible. It may have no class if the URL is not very long; otherwise, it will have an `ellipsis` class. No ellipsis (`…`) character is inserted in the markup; instead, you are expected to insert it yourself if you need it in your app.

## Filters {#filters}

### Server-side filtering (v2, Mastodon 4.0 and above) {#server-filtered}

If a filter applies to a Status, a corresponding FilterResult will be included in the `filtered` attribute. Clients should check this attribute for any matches and use them to apply the intended filter action.

However, client implementations may still want to perform their own rule matching client-side, as this would allow retroactively applying filter changes without re-fetching posts from the server. When doing so, they should take care to not ignore `filtered` entries for which there are other attributes than `keyword_matches`, so as to handle extensions of the filtering system (e.g. `status_matches`).

Matched filters need to be filtered based on context (`home`, `notifications`, `public`, `thread` or `profile`) and expiration date.

When at least one active matched filter has `hide` for `filter_action`, the post should not be shown at all. Otherwise, if at least one active matched filter has `warn` for `filter_action`, the post should be hidden with a warning, and the user should be able to reveal the post after being informed of which filters matched (identified by `title` rather than the exact matched keywords).

For extension purposes, unknown values for `filter_action` should be treated as `warn`.

### Client-side filtering (v1, prior to Mastodon 4.0) {#client-filtered}

Clients must do their own text filtering based on filters returned from the API. The server will apply `irreversible` filters for `home` and `notifications` contexts, but **anything else is still up to the client to filter**! If a status is somehow not removed by an `irreversible` filter, the client should still filter it.

Expired filters are not deleted by the server. They should no longer be applied, but they are still stored by the server, as users may update the expiry time to re-enable the filter. It is up to users to delete those filters eventually, if they wish to do so.

If `whole_word` is true, the client app should do the following:

* Define ‘word constituent characters’ for your app. In the official implementation, it’s `[A-Za-z0-9_]` in JavaScript, and `[[:word:]]` in Ruby. Ruby uses the POSIX character class (Letter | Mark | Decimal_Number | Connector_Punctuation).
* If the phrase starts with a word character, and if the previous character before the matched range is a word character, its matched range should be treated to not match.
* If the phrase ends with a word character, and if the next character after the matched range is a word character, its matched range should be treated to not match.

Please check `app/javascript/mastodon/selectors/index.js` and `app/lib/feed_manager.rb` in the Mastodon source code for more details.

{{< caption-link url="https://github.com/mastodon/mastodon/blob/main/app/javascript/mastodon/selectors/index.js" caption="app/javascript/mastodon/selectors/index.js" >}}

{{< caption-link url="https://github.com/mastodon/mastodon/blob/main/app/lib/feed_manager.rb" caption="app/lib/feed_manager.rb" >}}

## Focal points for cropping media thumbnails {#focal-points}

Server-side preview images are never cropped, to support a variety of apps and user interfaces. Therefore, the cropping must be done by those apps. To crop intelligently, focal points can be used to ensure a certain section of the image is always within the cropped viewport. See this [guide on how focal points are defined](https://github.com/jonom/jquery-focuspoint#1-calculate-your-images-focus-point). In summary, floating points range from -1.0 to 1.0, left-to-right or bottom-to-top. (0,0) is the center of the image. (0.5, 0.5) would be in the center of the upper-right quadrant. (-0.5, -0.5) would be in the center of the lower-left quadrant. For reference, thumbnails in the Mastodon front end are most commonly 16:9.

{{< figure src="assets/focal-points.jpg" caption="A demonstration of various focal points and their coordinates." >}}
