Build custom content types for use with XMTP
Be aware that your custom content type may not be automatically recognized or supported by other apps, which could result in the other apps overlooking or only displaying the fallback text for your custom content type.
Building a custom content type enables you to manage data in a way that is more personalized or specialized to the needs of your app.
For more common content types, you can usually find a standard or standards-track content type to serve your needs.
If your custom content type generates interest within the developer community, consider proposing it as a standard content type through the XIP process.
Create the content type
Create the custom content type by creating a new file
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
A test of this content type can be found in the following PR
import { ContentTypeId } from "@xmtp/xmtp-js";
import type { ContentCodec, EncodedContent } from "@xmtp/xmtp-js";
// Create a unique identifier for your content type
export const ContentTypeMultiplyNumbers = new ContentTypeId({
authorityId: 'your.domain',
typeId: 'multiply-number',
versionMajor: 1,
versionMinor: 0,
})
// Define the MultiplyNumbers class
export class MultiplyNumbers {
public num1: number
public num2: number
public result: number | undefined
constructor(num1: number, num2: number, result?: number) {
this.num1 = num1
this.num2 = num2
this.result = result
}
}
// Define the MultiplyCodec class
export class ContentTypeMultiplyNumberCodec
implements ContentCodec<MultiplyNumbers>
{
get contentType(): ContentTypeId {
return ContentTypeMultiplyNumbers
}
// The encode method accepts an object of MultiplyNumbers and encodes it as a byte array
encode(numbers: MultiplyNumbers): EncodedContent {
const { num1, num2 } = numbers
return {
type: ContentTypeMultiplyNumbers,
parameters: {},
content: new TextEncoder().encode(JSON.stringify({ num1, num2 })),
}
}
// The decode method decodes the byte array, parses the string into numbers (num1, num2), and returns their product
decode(encodedContent: EncodedContent): MultiplyNumbers {
const decodedContent = new TextDecoder().decode(encodedContent.content)
const { num1, num2 } = JSON.parse(decodedContent)
return new MultiplyNumbers(num1, num2, num1 * num2)
}
fallback(content: MultiplyNumbers): string | undefined {
return `Can’t display number content types. Number was ${JSON.stringify(
content
)}`
// return undefined to indicate that this content type should not be displayed if it's not supported by a client
}
// Set to true to enable push notifications for interoperable content types.
// Receiving clients must handle this field appropriately.
shouldPush(): boolean {
return true;
}
}
A test of this content type can be found in the following PR
import { ContentTypeId } from "@xmtp/xmtp-js";
import type { ContentCodec, EncodedContent } from "@xmtp/xmtp-js";
// Create a unique identifier for your content type
export const ContentTypeMultiplyNumbers = new ContentTypeId({
authorityId: 'your.domain',
typeId: 'multiply-number',
versionMajor: 1,
versionMinor: 0,
})
// Define the MultiplyNumbers class
export class MultiplyNumbers {
public num1: number
public num2: number
public result: number | undefined
constructor(num1: number, num2: number, result?: number) {
this.num1 = num1
this.num2 = num2
this.result = result
}
}
// Define the MultiplyCodec class
export class ContentTypeMultiplyNumberCodec
implements ContentCodec<MultiplyNumbers>
{
get contentType(): ContentTypeId {
return ContentTypeMultiplyNumbers
}
// The encode method accepts an object of MultiplyNumbers and encodes it as a byte array
encode(numbers: MultiplyNumbers): EncodedContent {
const { num1, num2 } = numbers
return {
type: ContentTypeMultiplyNumbers,
parameters: {},
content: new TextEncoder().encode(JSON.stringify({ num1, num2 })),
}
}
// The decode method decodes the byte array, parses the string into numbers (num1, num2), and returns their product
decode(encodedContent: EncodedContent): MultiplyNumbers {
const decodedContent = new TextDecoder().decode(encodedContent.content)
const { num1, num2 } = JSON.parse(decodedContent)
return new MultiplyNumbers(num1, num2, num1 * num2)
}
fallback(content: MultiplyNumbers): string | undefined {
return `Can’t display number content types. Number was ${JSON.stringify(
content
)}`
// return undefined to indicate that this content type should not be displayed if it's not supported by a client
}
// This method is optional and can be used to determine if the content type should have a push notification
shouldPush() {
return true;
}
}
export const multiplyNumbersContentTypeConfig: ContentTypeConfiguration = {
codecs: [new ContentTypeMultiplyNumberCodec()],
contentTypes: [ContentTypeMultiplyNumbers.toString()],
namespace: NAMESPACE, // Replace with the actual namespace you are using
processors: {
[ContentTypeMultiplyNumbers.toString()]: [processMultiplyNumbers],
},
validators: {
[ContentTypeMultiplyNumbers.toString()]: isValidMultiplyNumbersContent,
},
};
// You'll need to define the processMultiplyNumbers and isValidMultiplyNumbersContent functions
// These should be tailored to handle the specific logic of processing and validating
// the content type for multiplying numbers.
function processMultiplyNumbers(content) {
// Define how to process the multiply numbers content
// Example: logging, storing, or manipulating the data
}
function isValidMultiplyNumbersContent(content) {
// Define validation logic for multiply numbers content
// Example: checking if the numbers are valid, not null, etc.
}
import org.json.JSONObject
import org.xmtp.android.library.codecs.ContentTypeId
import org.xmtp.android.library.codecs.ContentTypeIdBuilder
import org.xmtp.android.library.codecs.ContentCodec
import org.xmtp.android.library.codecs.EncodedContent
data class NumberPair(val a: Double, val b: Double, var result: Double = 0.0)
data class MultiplyNumberCodec(
override var contentType: ContentTypeId = ContentTypeIdBuilder.builderFromAuthorityId(
authorityId = "your.domain",
typeId = "multiply-number",
versionMajor = 1,
versionMinor = 0
)
) : ContentCodec<NumberPair> {
override fun encode(content: NumberPair): EncodedContent {
val jsonObject = JSONObject()
jsonObject.put("a", content.a)
jsonObject.put("b", content.b)
return EncodedContent.newBuilder().also {
it.type = contentType
it.content = jsonObject.toString().toByteStringUtf8()
}.build()
}
override fun decode(content: EncodedContent): NumberPair {
val jsonObject = JSONObject(content.content.toStringUtf8())
val a = jsonObject.getDouble("a")
val b = jsonObject.getDouble("b")
val numberPair = NumberPair(a, b, a * b)
return numberPair
}
override fun fallback(content: NumberPair): String? {
return "Error: This app does not support numbers."
}
// This method is optional and can be used to determine if the content type should have a push notification
override fun shouldPush(): Boolean {
return true;
}
}
A test of this content type can be found in the following PR
import XMTP
public struct MultiplyNumbers {
public var num1: Double
public var num2: Double
public var result: Double?
public init(num1: Double, num2: Double, result: Double? = nil) {
self.num1 = num1
self.num2 = num2
self.result = result
}
}
public struct MultiplyNumbersCodec: ContentCodec {
public typealias T = MultiplyNumbers
public var contentType: ContentTypeID {
ContentTypeID(authorityID: "example.com", typeID: "number", versionMajor: 1, versionMinor: 1)
}
public func encode(content: MultiplyNumbers, client: Client) throws -> EncodedContent {
var encodedContent = EncodedContent()
encodedContent.type = contentType
encodedContent.parameters["num1"] = String(content.num1)
encodedContent.parameters["num2"] = String(content.num2)
return encodedContent
}
public func decode(content: EncodedContent, client _: Client) throws -> MultiplyNumbers {
guard let num1Str = content.parameters["num1"], let num1 = Double(num1Str),
let num2Str = content.parameters["num2"], let num2 = Double(num2Str) else {
throw CodecError.invalidContent
}
return MultiplyNumbers(num1: num1, num2: num2, result: num1 * num2)
}
public func fallback(content: MultiplyNumbers) throws -> String? {
return "MultiplyNumbersCodec is not supported"
}
// This method is optional and can be used to determine if the content type should have a push notification
public func shouldPush() -> Bool {
return true;
}
}
A test of this content type can be found in the following PR
import 'dart:convert';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
import 'package:xmtp/xmtp.dart' as xmtp;
import 'package:xmtp/src/content/codec.dart';
import 'package:xmtp/src/client.dart';
// Class to store two numbers and their multiplication result
class MultiplyNumbers {
final double num1;
final double num2;
final double result;
MultiplyNumbers({required this.num1, required this.num2, required this.result});
}
/// This encodes two numbers and their multiplication result.
final contentTypeMultiplyNumbers = xmtp.ContentTypeId(
authorityId: "com.example",
typeId: "multiplyNumbers",
versionMajor: 1,
versionMinor: 1,
);
// Codec class to encode and decode the multiplication result
class MultiplyNumbersCodec extends Codec<MultiplyNumbers> {
@override
// Content type for the codec
xmtp.ContentTypeId get contentType => contentTypeMultiplyNumbers;
@override
// Function to encode the multiplication result
Future<xmtp.EncodedContent> encode(MultiplyNumbers decoded) async => xmtp.EncodedContent(
type: contentTypeMultiplyNumbers,
parameters: {
'num1': decoded.num1.toString(),
'num2': decoded.num2.toString(),
},
);
@override
// Function to decode the encoded content
Future<MultiplyNumbers> decode(xmtp.EncodedContent encoded) async {
var num1 = double.parse(encoded.parameters['num1'] ?? '0');
var num2 = double.parse(encoded.parameters['num2'] ?? '0');
return MultiplyNumbers(num1: num1, num2: num2, result: num1 * num2);
}
@override
// Fallback function in case the codec is not supported
String fallback(MultiplyNumbers content) => "MultiplyNumbersCodec is not supported";
// This method is optional and can be used to determine if the content type should have a push notification
@override
bool shouldPush() => true;
}
import { content } from '@xmtp/proto'
import {JSContentCodec, Client, Conversation, DecodedMessage } from '@xmtp/react-native-sdk';
type EncodedContent = content.EncodedContent
type ContentTypeId = content.ContentTypeId
const ContentTypeMultiplyNumbers: ContentTypeId = {
authorityId: 'com.example',
typeId: 'multiplyNumbers',
versionMajor: 1,
versionMinor: 1,
}
class MultiplyNumbers {
public readonly num1: number
public readonly num2: number
public readonly result: number
constructor(num1: number, num2: number, result: number) {
this.num1 = num1
this.num2 = num2
this.result = result
}
}
class ContentTypeMultiplyNumberCodec
implements JSContentCodec<MultiplyNumbers>
{
get contentType() {
return ContentTypeMultiplyNumbers
}
encode(decoded: MultiplyNumbers): EncodedContent {
return {
type: ContentTypeMultiplyNumbers,
parameters: {
num1: decoded.num1.toString(),
num2: decoded.num2.toString(),
},
content: new Uint8Array(),
}
}
decode(encoded: EncodedContent): MultiplyNumbers {
const num1 = parseFloat(encoded.parameters['num1'] ?? '0')
const num2 = parseFloat(encoded.parameters['num2'] ?? '0')
return new MultiplyNumbers(num1, num2, num1 * num2)
}
fallback(content: MultiplyNumbers): string {
return `MultiplyNumbersCodec is not supported`
}
// This method is optional and can be used to determine if the content type should trigger a push notification
shouldPush(): boolean {
return true;
}
}
Configure the content types
Import and register the custom content type.
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
import { ContentTypeMultiplyNumberCodec } from "./xmtp-content-type-multiply-number";
const client = await Client.create({
env: "production",
codecs: [new ContentTypeMultiplyNumberCodec()],
});
//or
client.registerCodec(new ContentTypeMultiplyNumberCodec());
import {
XMTPProvider,
} from "@xmtp/react-sdk";
import {multiplyNumbersContentTypeConfig} from "./xmtp-content-type-multiply-number";
const contentTypeConfigs = [
multiplyNumbersContentTypeConfig,
];
createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<XMTPProvider contentTypeConfigs={contentTypeConfigs}>
<App />
</XMTPProvider>
</StrictMode>,
);
import org.xmtp.android.library.codecs.ContentTypeMultiplyNumberCodec
Client.register(codec = ContentTypeMultiplyNumberCodec())
Client.register(codec: ContentTypeMultiplyNumberCodec())
var client = await Client.createFromWallet(api, wallet,customCodecs: [MultiplyNumbersCodec()]);
import { ContentTypeMultiplyNumberCodec } from "./xmtp-content-type-number";
const client = await Client.create({
env: "production",
codecs: [new ContentTypeMultiplyNumberCodec()],
});
Send the content
Send a message using the custom content type. This code sample demonstrates how to use the MultiplyCodec
custom content type to perform multiplication operations.
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
const numbersToMultiply = new MultiplyNumbers(2, 3);
conversation.send(numbersToMultiply, {
contentType: ContentTypeMultiplyNumbers,
});
const numbersToMultiply = { a: 3, b: 7 };
conversation.send(numbersToMultiply, {
contentType: ContentTypeMultiplyNumbers,
});
import org.xmtp.android.library.codecs.ContentTypeMultiplyNumberCodec
val numbers = NumberPair(
a = 3,
b = 7,
)
conversation.send(
content = numbers,
options = SendOptions(contentType = ContentTypeMultiplyNumberCodec().contentType),
)
let multiplyNumbers = MultiplyNumbers(num1: 3, num2: 2)
try await aliceConversation.send(content: multiplyNumbers, options: .init(contentType: ContentTypeMultiplyNumberCodec().contentType))
// Creating a conversation between Alice and Bob
var aliceConvo = await alice.newConversation(bob.address.toString());
// Multiplying two numbers and storing the result
var multiplyNumbers = MultiplyNumbers(num1: 12, num2: 34, result: 12*34);
// Encoding the multiplication result
var encodedMultiplyNumbers = await MultiplyNumbersCodec().encode(multiplyNumbers);
// Alice sends the encoded message
await alice.sendMessageEncoded(aliceConvo, encodedMultiplyNumbers);
const multiplyNumbers = new MultiplyNumbers(3, 7);
await bobConvo.send(multiplyNumbers, {
contentType: ContentTypeMultiplyNumbers,
});
Receive the content
To use the result of the multiplication operation, add a renderer for the custom content type.
- JavaScript
- React
- Swift
- Kotlin
- Dart
- React Native
if (message.contentType.sameAs(ContentTypeMultiplyNumber)) {
return message.content; // 21
}
if (message.contentType.sameAs(ContentTypeMultiplyNumber)) {
return message.content; // 21
}
if let content: MultiplyNumbers = try? messages[0].content() {
//content.result
}
if (bobMessages[0].contentType == contentTypeMultiplyNumbers) {
(bobMessages[0].content as MultiplyNumbers).result// 408.0
}
Because of this message content is now a function which returns the actual content. You can get that content by call message.content()
now instead of message.content . This may involve more filtering on the message side to make sure you are handling different contentTypes appropriately.
if (message.contentTypeId === "com.example/multiplyNumbers:1.1") {
return message.content(); // 21
}
To handle unsupported content types, refer to the fallback section.