diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index 99f4d93055403d39b8ebfb7e1b9d7eea861c08e5..7e0b16c25dcb87c2241fcd745172a17a9a3f63c0 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -1,30 +1,23 @@
 # frozen_string_literal: true
 
 class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
-  CONTEXT = {
-    '@context': [
-      'https://www.w3.org/ns/activitystreams',
-      'https://w3id.org/security/v1',
-
-      {
-        'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
-        'sensitive'                 => 'as:sensitive',
-        'movedTo'                   => { '@id' => 'as:movedTo', '@type' => '@id' },
-        'alsoKnownAs'               => { '@id' => 'as:alsoKnownAs', '@type' => '@id' },
-        'Hashtag'                   => 'as:Hashtag',
-        'ostatus'                   => 'http://ostatus.org#',
-        'atomUri'                   => 'ostatus:atomUri',
-        'inReplyToAtomUri'          => 'ostatus:inReplyToAtomUri',
-        'conversation'              => 'ostatus:conversation',
-        'toot'                      => 'http://joinmastodon.org/ns#',
-        'Emoji'                     => 'toot:Emoji',
-        'focalPoint'                => { '@container' => '@list', '@id' => 'toot:focalPoint' },
-        'featured'                  => { '@id' => 'toot:featured', '@type' => '@id' },
-        'schema'                    => 'http://schema.org#',
-        'PropertyValue'             => 'schema:PropertyValue',
-        'value'                     => 'schema:value',
-      },
-    ],
+  NAMED_CONTEXT_MAP = {
+    activitystreams: 'https://www.w3.org/ns/activitystreams',
+    security: 'https://w3id.org/security/v1',
+  }.freeze
+
+  CONTEXT_EXTENSION_MAP = {
+    manually_approves_followers: { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers' },
+    sensitive: { 'sensitive' => 'as:sensitive' },
+    hashtag: { 'Hashtag' => 'as:Hashtag' },
+    moved_to: { 'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' } },
+    also_known_as: { 'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' } },
+    emoji: { 'toot' => 'http://joinmastodon.org/ns#', 'Emoji' => 'toot:Emoji' },
+    featured: { 'toot' => 'http://joinmastodon.org/ns#', 'featured' => { '@id' => 'toot:featured', '@type' => '@id' } },
+    property_value: { 'schema' => 'http://schema.org#', 'PropertyValue' => 'schema:PropertyValue', 'value' => 'schema:value' },
+    atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' },
+    conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
+    focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
   }.freeze
 
   def self.default_key_transform
@@ -36,8 +29,36 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
   end
 
   def serializable_hash(options = nil)
-    options = serialization_options(options)
-    serialized_hash = ActiveModelSerializers::Adapter::Attributes.new(serializer, instance_options).serializable_hash(options)
-    CONTEXT.merge(self.class.transform_key_casing!(serialized_hash, instance_options))
+    options         = serialization_options(options)
+    serialized_hash = serializer.serializable_hash(options)
+    serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options)
+
+    { '@context' => serialized_context }.merge(serialized_hash)
+  end
+
+  private
+
+  def serialized_context
+    context_array = []
+
+    serializer_options = serializer.send(:instance_options) || {}
+    named_contexts     = [:activitystreams] + serializer._named_contexts.keys + serializer_options.fetch(:named_contexts, {}).keys
+    context_extensions = serializer._context_extensions.keys + serializer_options.fetch(:context_extensions, {}).keys
+
+    named_contexts.each do |key|
+      context_array << NAMED_CONTEXT_MAP[key]
+    end
+
+    extensions = context_extensions.each_with_object({}) do |key, h|
+      h.merge!(CONTEXT_EXTENSION_MAP[key])
+    end
+
+    context_array << extensions unless extensions.empty?
+
+    if context_array.size == 1
+      context_array.first
+    else
+      context_array
+    end
   end
 end
diff --git a/app/lib/activitypub/serializer.rb b/app/lib/activitypub/serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..07bd8c494612e2117d3d1dd55fc34cd9953ced9b
--- /dev/null
+++ b/app/lib/activitypub/serializer.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class ActivityPub::Serializer < ActiveModel::Serializer
+  with_options instance_writer: false, instance_reader: true do |serializer|
+    serializer.class_attribute :_named_contexts
+    serializer.class_attribute :_context_extensions
+
+    self._named_contexts     ||= {}
+    self._context_extensions ||= {}
+  end
+
+  def self.inherited(base)
+    super
+
+    base._named_contexts     = _named_contexts.dup
+    base._context_extensions = _context_extensions.dup
+  end
+
+  def self.context(*named_contexts)
+    named_contexts.each do |context|
+      _named_contexts[context] = true
+    end
+  end
+
+  def self.context_extensions(*extension_names)
+    extension_names.each do |extension_name|
+      _context_extensions[extension_name] = true
+    end
+  end
+end
diff --git a/app/serializers/activitypub/accept_follow_serializer.rb b/app/serializers/activitypub/accept_follow_serializer.rb
index 3e23591a527492ee5b2869bf1b0293e9d2ffef21..1c1c6ab73bb5783db05276c865d429cadf1fb6c8 100644
--- a/app/serializers/activitypub/accept_follow_serializer.rb
+++ b/app/serializers/activitypub/accept_follow_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::AcceptFollowSerializer < ActiveModel::Serializer
+class ActivityPub::AcceptFollowSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
 
   has_one :object, serializer: ActivityPub::FollowSerializer
diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb
index c001e28aae472b7a11c509280b5996234148cc94..c06d5c87ca3855f0a9c6d6dd20c10ec1eebec1df 100644
--- a/app/serializers/activitypub/activity_serializer.rb
+++ b/app/serializers/activitypub/activity_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::ActivitySerializer < ActiveModel::Serializer
+class ActivityPub::ActivitySerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :published, :to, :cc
 
   has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, if: :serialize_object?
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 6746c1782929a94fb622de10ede547f2c822c271..4b982b95511a22fc59fd777c3148dd9aa4223b3c 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -1,8 +1,13 @@
 # frozen_string_literal: true
 
-class ActivityPub::ActorSerializer < ActiveModel::Serializer
+class ActivityPub::ActorSerializer < ActivityPub::Serializer
   include RoutingHelper
 
+  context :security
+
+  context_extensions :manually_approves_followers, :featured, :also_known_as,
+                     :moved_to, :property_value, :hashtag, :emoji
+
   attributes :id, :type, :following, :followers,
              :inbox, :outbox, :featured,
              :preferred_username, :name, :summary,
@@ -16,7 +21,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
   attribute :moved_to, if: :moved?
   attribute :also_known_as, if: :also_known_as?
 
-  class EndpointsSerializer < ActiveModel::Serializer
+  class EndpointsSerializer < ActivityPub::Serializer
     include RoutingHelper
 
     attributes :shared_inbox
@@ -124,7 +129,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
   class CustomEmojiSerializer < ActivityPub::EmojiSerializer
   end
 
-  class TagSerializer < ActiveModel::Serializer
+  class TagSerializer < ActivityPub::Serializer
     include RoutingHelper
 
     attributes :type, :href, :name
@@ -142,7 +147,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
     end
   end
 
-  class Account::FieldSerializer < ActiveModel::Serializer
+  class Account::FieldSerializer < ActivityPub::Serializer
     attributes :type, :name, :value
 
     def type
diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb
index c0906e8d036418d5318390b107602a266786a706..6f5aab17f95ddb86e2f864e4fa28b4eafd71d165 100644
--- a/app/serializers/activitypub/add_serializer.rb
+++ b/app/serializers/activitypub/add_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::AddSerializer < ActiveModel::Serializer
+class ActivityPub::AddSerializer < ActivityPub::Serializer
   include RoutingHelper
 
   attributes :type, :actor, :target
diff --git a/app/serializers/activitypub/block_serializer.rb b/app/serializers/activitypub/block_serializer.rb
index 624ce2fce8107b9ff37786f7a825a991b37b5069..e6c69329d8cb26c761ba0f1e0f4b22ab6136183d 100644
--- a/app/serializers/activitypub/block_serializer.rb
+++ b/app/serializers/activitypub/block_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::BlockSerializer < ActiveModel::Serializer
+class ActivityPub::BlockSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
   attribute :virtual_object, key: :object
 
diff --git a/app/serializers/activitypub/collection_serializer.rb b/app/serializers/activitypub/collection_serializer.rb
index b03609957d104be9b23f00c7b3bff4423c2e62fa..da1ba735fc25063c69c83e4aedb88796ce7ed55d 100644
--- a/app/serializers/activitypub/collection_serializer.rb
+++ b/app/serializers/activitypub/collection_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::CollectionSerializer < ActiveModel::Serializer
+class ActivityPub::CollectionSerializer < ActivityPub::Serializer
   def self.serializer_for(model, options)
     return ActivityPub::NoteSerializer if model.class.name == 'Status'
     return ActivityPub::CollectionSerializer if model.class.name == 'ActivityPub::CollectionPresenter'
diff --git a/app/serializers/activitypub/delete_actor_serializer.rb b/app/serializers/activitypub/delete_actor_serializer.rb
index ddf59be9709ef1cc21af267f935fbb28611ac1cb..a6c5e2385eb266b6e1eed613456adcfe1c92cae3 100644
--- a/app/serializers/activitypub/delete_actor_serializer.rb
+++ b/app/serializers/activitypub/delete_actor_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::DeleteActorSerializer < ActiveModel::Serializer
+class ActivityPub::DeleteActorSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :to
   attribute :virtual_object, key: :object
 
diff --git a/app/serializers/activitypub/delete_serializer.rb b/app/serializers/activitypub/delete_serializer.rb
index 5012a8383ff517003906f65474636f6b7e2da4db..a7d5bd469856fb6ab1e98472249dadfd56d55066 100644
--- a/app/serializers/activitypub/delete_serializer.rb
+++ b/app/serializers/activitypub/delete_serializer.rb
@@ -1,7 +1,9 @@
 # frozen_string_literal: true
 
-class ActivityPub::DeleteSerializer < ActiveModel::Serializer
-  class TombstoneSerializer < ActiveModel::Serializer
+class ActivityPub::DeleteSerializer < ActivityPub::Serializer
+  class TombstoneSerializer < ActivityPub::Serializer
+    context_extensions :atom_uri
+
     attributes :id, :type, :atom_uri
 
     def id
diff --git a/app/serializers/activitypub/emoji_serializer.rb b/app/serializers/activitypub/emoji_serializer.rb
index 7b06b1e5db2aa5a23814a977c740040774236b4c..4dc38f3ea6e571d6c0d1a362a9f88fb392cf5b90 100644
--- a/app/serializers/activitypub/emoji_serializer.rb
+++ b/app/serializers/activitypub/emoji_serializer.rb
@@ -1,8 +1,10 @@
 # frozen_string_literal: true
 
-class ActivityPub::EmojiSerializer < ActiveModel::Serializer
+class ActivityPub::EmojiSerializer < ActivityPub::Serializer
   include RoutingHelper
 
+  context_extensions :emoji
+
   attributes :id, :type, :name, :updated
 
   has_one :icon, serializer: ActivityPub::ImageSerializer
diff --git a/app/serializers/activitypub/flag_serializer.rb b/app/serializers/activitypub/flag_serializer.rb
index 1e7a46dd9ed486a215a65ed74129b5390acb2c61..2f2a707d36299719edb2ca0973b997ac9f81fd7a 100644
--- a/app/serializers/activitypub/flag_serializer.rb
+++ b/app/serializers/activitypub/flag_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::FlagSerializer < ActiveModel::Serializer
+class ActivityPub::FlagSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :content
   attribute :virtual_object, key: :object
 
diff --git a/app/serializers/activitypub/follow_serializer.rb b/app/serializers/activitypub/follow_serializer.rb
index bb204ee8f36916e4b1a3a4ca582f86861b93249e..9228d7716e31d7b91f617857775dce81d74f923a 100644
--- a/app/serializers/activitypub/follow_serializer.rb
+++ b/app/serializers/activitypub/follow_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::FollowSerializer < ActiveModel::Serializer
+class ActivityPub::FollowSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
   attribute :virtual_object, key: :object
 
diff --git a/app/serializers/activitypub/image_serializer.rb b/app/serializers/activitypub/image_serializer.rb
index 3c08f77e8313985ff63d42d13dd0870efc2c4998..1060f969149b03cd7a4a5bb87f2813f53afbfd25 100644
--- a/app/serializers/activitypub/image_serializer.rb
+++ b/app/serializers/activitypub/image_serializer.rb
@@ -1,8 +1,10 @@
 # frozen_string_literal: true
 
-class ActivityPub::ImageSerializer < ActiveModel::Serializer
+class ActivityPub::ImageSerializer < ActivityPub::Serializer
   include RoutingHelper
 
+  context_extensions :focal_point
+
   attributes :type, :media_type, :url
   attribute :focal_point, if: :focal_point?
 
diff --git a/app/serializers/activitypub/like_serializer.rb b/app/serializers/activitypub/like_serializer.rb
index c1a7ff6f63743e210afd128004d2523c5af0f7af..0f170ddb4e82777d2e2ae8e4b5bf2e3199b6e29f 100644
--- a/app/serializers/activitypub/like_serializer.rb
+++ b/app/serializers/activitypub/like_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::LikeSerializer < ActiveModel::Serializer
+class ActivityPub::LikeSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
   attribute :virtual_object, key: :object
 
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 553f333d8ff00aa22302894ab45a1d92db931ef3..0666bea5a786627cf92794fda117f8dfddea6514 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -1,6 +1,9 @@
 # frozen_string_literal: true
 
-class ActivityPub::NoteSerializer < ActiveModel::Serializer
+class ActivityPub::NoteSerializer < ActivityPub::Serializer
+  context_extensions :atom_uri, :conversation, :sensitive,
+                     :hashtag, :emoji, :focal_point
+
   attributes :id, :type, :summary,
              :in_reply_to, :published, :url,
              :attributed_to, :to, :cc, :sensitive,
@@ -147,7 +150,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
     object.poll&.expired?
   end
 
-  class MediaAttachmentSerializer < ActiveModel::Serializer
+  class MediaAttachmentSerializer < ActivityPub::Serializer
     include RoutingHelper
 
     attributes :type, :media_type, :url, :name
@@ -178,7 +181,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
     end
   end
 
-  class MentionSerializer < ActiveModel::Serializer
+  class MentionSerializer < ActivityPub::Serializer
     attributes :type, :href, :name
 
     def type
@@ -194,7 +197,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
     end
   end
 
-  class TagSerializer < ActiveModel::Serializer
+  class TagSerializer < ActivityPub::Serializer
     include RoutingHelper
 
     attributes :type, :href, :name
@@ -215,8 +218,8 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
   class CustomEmojiSerializer < ActivityPub::EmojiSerializer
   end
 
-  class OptionSerializer < ActiveModel::Serializer
-    class RepliesSerializer < ActiveModel::Serializer
+  class OptionSerializer < ActivityPub::Serializer
+    class RepliesSerializer < ActivityPub::Serializer
       attributes :type, :total_items
 
       def type
diff --git a/app/serializers/activitypub/public_key_serializer.rb b/app/serializers/activitypub/public_key_serializer.rb
index 38e9e93baaac97dc6d9b42676df9b875ea0c9116..62ed49e81d27657fc0fc624fb3985052df53ad0a 100644
--- a/app/serializers/activitypub/public_key_serializer.rb
+++ b/app/serializers/activitypub/public_key_serializer.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
-class ActivityPub::PublicKeySerializer < ActiveModel::Serializer
+class ActivityPub::PublicKeySerializer < ActivityPub::Serializer
+  context :security
+
   attributes :id, :owner, :public_key_pem
 
   def id
diff --git a/app/serializers/activitypub/reject_follow_serializer.rb b/app/serializers/activitypub/reject_follow_serializer.rb
index 7814f4f5759d450b8a7d58c1b0a9345c767634f2..4996c9a3c35455e76dadc94d68f3b8d2e525da83 100644
--- a/app/serializers/activitypub/reject_follow_serializer.rb
+++ b/app/serializers/activitypub/reject_follow_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::RejectFollowSerializer < ActiveModel::Serializer
+class ActivityPub::RejectFollowSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
 
   has_one :object, serializer: ActivityPub::FollowSerializer
diff --git a/app/serializers/activitypub/remove_serializer.rb b/app/serializers/activitypub/remove_serializer.rb
index c2a5ae1b3a8ac496cf866e27946c308e1f3c5a1c..7fefda59da3ee839cb8196d4b5eed9f21b44249b 100644
--- a/app/serializers/activitypub/remove_serializer.rb
+++ b/app/serializers/activitypub/remove_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::RemoveSerializer < ActiveModel::Serializer
+class ActivityPub::RemoveSerializer < ActivityPub::Serializer
   include RoutingHelper
 
   attributes :type, :actor, :target
diff --git a/app/serializers/activitypub/undo_announce_serializer.rb b/app/serializers/activitypub/undo_announce_serializer.rb
index 4fc042727ad6898aae581d824332ba6df3372421..6758af6792afdd4afc845a7d74a92aad6c33bf2d 100644
--- a/app/serializers/activitypub/undo_announce_serializer.rb
+++ b/app/serializers/activitypub/undo_announce_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UndoAnnounceSerializer < ActiveModel::Serializer
+class ActivityPub::UndoAnnounceSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :to
 
   has_one :object, serializer: ActivityPub::ActivitySerializer
diff --git a/app/serializers/activitypub/undo_block_serializer.rb b/app/serializers/activitypub/undo_block_serializer.rb
index 2f43d8402a38a259ade75353940c5a18f96bc72e..b4f049377715504b1b2b3de64d5cb2bc7f70d804 100644
--- a/app/serializers/activitypub/undo_block_serializer.rb
+++ b/app/serializers/activitypub/undo_block_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UndoBlockSerializer < ActiveModel::Serializer
+class ActivityPub::UndoBlockSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
 
   has_one :object, serializer: ActivityPub::BlockSerializer
diff --git a/app/serializers/activitypub/undo_follow_serializer.rb b/app/serializers/activitypub/undo_follow_serializer.rb
index e5b7f143d793428a7859cbe0439f5606b901a4bf..9b3e0ca3cb23b1c10045a930f660744c67bb2fbc 100644
--- a/app/serializers/activitypub/undo_follow_serializer.rb
+++ b/app/serializers/activitypub/undo_follow_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UndoFollowSerializer < ActiveModel::Serializer
+class ActivityPub::UndoFollowSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
 
   has_one :object, serializer: ActivityPub::FollowSerializer
diff --git a/app/serializers/activitypub/undo_like_serializer.rb b/app/serializers/activitypub/undo_like_serializer.rb
index 25f4ccaaed5ac120fc777f165f199b305c7b08bd..20c786cb76966c3257f29efe81ff1039a2e5973b 100644
--- a/app/serializers/activitypub/undo_like_serializer.rb
+++ b/app/serializers/activitypub/undo_like_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UndoLikeSerializer < ActiveModel::Serializer
+class ActivityPub::UndoLikeSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor
 
   has_one :object, serializer: ActivityPub::LikeSerializer
diff --git a/app/serializers/activitypub/update_poll_serializer.rb b/app/serializers/activitypub/update_poll_serializer.rb
index f7933346febc2c3e9268eed4c5582d4b79be9532..a9a09747f8ce098d28d0c47f13c0c4fd76e0991d 100644
--- a/app/serializers/activitypub/update_poll_serializer.rb
+++ b/app/serializers/activitypub/update_poll_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UpdatePollSerializer < ActiveModel::Serializer
+class ActivityPub::UpdatePollSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :to
 
   has_one :object, serializer: ActivityPub::NoteSerializer
diff --git a/app/serializers/activitypub/update_serializer.rb b/app/serializers/activitypub/update_serializer.rb
index 48d7a192946a70875e79fc5ee8f6d44e605674c4..a5eb857d3fe6dce8245c4bff5ffb170157c2fce7 100644
--- a/app/serializers/activitypub/update_serializer.rb
+++ b/app/serializers/activitypub/update_serializer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ActivityPub::UpdateSerializer < ActiveModel::Serializer
+class ActivityPub::UpdateSerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :to
 
   has_one :object, serializer: ActivityPub::ActorSerializer
diff --git a/app/serializers/activitypub/vote_serializer.rb b/app/serializers/activitypub/vote_serializer.rb
index 248190404e840f4f71a16e787a9feffca8021503..71ef5c77e3daf07881ab904c5c4360cf64f17c80 100644
--- a/app/serializers/activitypub/vote_serializer.rb
+++ b/app/serializers/activitypub/vote_serializer.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
-class ActivityPub::VoteSerializer < ActiveModel::Serializer
-  class NoteSerializer < ActiveModel::Serializer
+class ActivityPub::VoteSerializer < ActivityPub::Serializer
+  class NoteSerializer < ActivityPub::Serializer
     attributes :id, :type, :name, :attributed_to,
                :in_reply_to, :to
 
diff --git a/config/initializers/active_model_serializers.rb b/config/initializers/active_model_serializers.rb
index 0e69e1d96c408ded0bb5d4d08a5b132408709771..329a5fb2c3a005688bdf9a5e023815008a62f974 100644
--- a/config/initializers/active_model_serializers.rb
+++ b/config/initializers/active_model_serializers.rb
@@ -3,3 +3,22 @@ ActiveModelSerializers.config.tap do |config|
 end
 
 ActiveSupport::Notifications.unsubscribe(ActiveModelSerializers::Logging::RENDER_EVENT)
+
+class ActiveModel::Serializer::Reflection
+  # We monkey-patch this method so that when we include associations in a serializer,
+  # the nested serializers can send information about used contexts upwards back to
+  # the root. We do this via instance_options because the nesting can be dynamic.
+  def build_association(parent_serializer, parent_serializer_options, include_slice = {})
+    serializer = options[:serializer]
+
+    parent_serializer_options.merge!(named_contexts: serializer._named_contexts, context_extensions: serializer._context_extensions) if serializer.respond_to?(:_named_contexts)
+
+    association_options = {
+      parent_serializer: parent_serializer,
+      parent_serializer_options: parent_serializer_options,
+      include_slice: include_slice,
+    }
+
+    ActiveModel::Serializer::Association.new(self, association_options)
+  end
+end
diff --git a/spec/lib/activitypub/adapter_spec.rb b/spec/lib/activitypub/adapter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ea03797aab2819a7a5da31456ae7928150cc9d1a
--- /dev/null
+++ b/spec/lib/activitypub/adapter_spec.rb
@@ -0,0 +1,88 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Adapter do
+  class TestObject < ActiveModelSerializers::Model
+    attributes :foo
+  end
+
+  class TestWithBasicContextSerializer < ActivityPub::Serializer
+    attributes :foo
+  end
+
+  class TestWithNamedContextSerializer < ActivityPub::Serializer
+    context :security
+    attributes :foo
+  end
+
+  class TestWithNestedNamedContextSerializer < ActivityPub::Serializer
+    attributes :foo
+
+    has_one :virtual_object, key: :baz, serializer: TestWithNamedContextSerializer
+
+    def virtual_object
+      object
+    end
+  end
+
+  class TestWithContextExtensionSerializer < ActivityPub::Serializer
+    context_extensions :sensitive
+    attributes :foo
+  end
+
+  class TestWithNestedContextExtensionSerializer < ActivityPub::Serializer
+    context_extensions :manually_approves_followers
+    attributes :foo
+
+    has_one :virtual_object, key: :baz, serializer: TestWithContextExtensionSerializer
+
+    def virtual_object
+      object
+    end
+  end
+
+  describe '#serializable_hash' do
+    let(:serializer_class) {}
+
+    subject { ActiveModelSerializers::SerializableResource.new(TestObject.new(foo: 'bar'), serializer: serializer_class, adapter: described_class).as_json }
+
+    context 'when serializer defines no context' do
+      let(:serializer_class) { TestWithBasicContextSerializer }
+
+      it 'renders a basic @context' do
+        expect(subject).to include({ '@context' => 'https://www.w3.org/ns/activitystreams' })
+      end
+    end
+
+    context 'when serializer defines a named context' do
+      let(:serializer_class) { TestWithNamedContextSerializer }
+
+      it 'renders a @context with both items' do
+        expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
+      end
+    end
+
+    context 'when serializer has children that define a named context' do
+      let(:serializer_class) { TestWithNestedNamedContextSerializer }
+
+      it 'renders a @context with both items' do
+        expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })
+      end
+    end
+
+    context 'when serializer defines context extensions' do
+      let(:serializer_class) { TestWithContextExtensionSerializer }
+
+      it 'renders a @context with the extension' do
+        expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'sensitive' => 'as:sensitive' }] })
+      end
+    end
+
+    context 'when serializer has children that define context extensions' do
+      let(:serializer_class) { TestWithNestedContextExtensionSerializer }
+
+      it 'renders a @context with both extensions' do
+        expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive' }] })
+      end
+    end
+  end
+end