Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated May 5, 2024

Add custom real-time subscriptions

Create a custom real-time subscription for any mutation to enable PubSub use cases.

Define a custom subscription

For every custom subscription, you need to set:

  1. the mutation(s) that should trigger a subscription event,
  2. a return type that matches the subscribed mutations' return type,
  3. authorization rules.

Optionally, you can set filter arguments to customize the server-side subscription filter rules.

Use a.subscription() to define your custom subscription in your amplify/data/resource.ts file:

amplify/data/resource.ts
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
const schema = a.schema({
// Message type that's used for this PubSub sample
Message: a.customType({
content: a.string().required(),
channelName: a.string().required()
}),
// Message publish mutation
publish: a.mutation()
.arguments({
channelName: a.string().required(),
content: a.string().required()
})
.returns(a.ref('Message'))
.handler(a.handler.custom({ entry: './publish.js' }))
.authorization(allow => [allow.publicApiKey()]),
// A data model to manage channels
Channel: a.model({
name: a.string(),
}).authorization(allow => [allow.publicApiKey()]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema
});

For this example, we're building a generic PubSub capability. This requires us to convert the arguments for publish into the Channel's format. Create a new publish.js file in your amplify/data/ folder with the following contents:

amplify/data/publish.js
// This handler simply passes through the arguments of the mutation through as the result
export function request() {
return {}
}
/**
* @param {import('@aws-appsync/utils').Context} ctx
*/
export function response(ctx) {
return ctx.args
}

Next, create a new receive.js file in your amplify/data/ folder to define handlers for your subscription. In this case, it'll just be a simple passthrough. In the next section, we'll explore how to use this handler to construct more advanced subscription filters.

Note: We're planning a developer experience enhancement in the near future that'll create this passthrough under the hood.

amplify/data/receive.js
export function request() {
return {};
}
export const response = (ctx) => {
return ctx.result;
};

Subscribe to custom subscriptions client-side

From your generated Data client, you can find all your custom subscriptions under client.subscriptions. Subscribe using the .subscribe() function and then use the next function to handle incoming events.

import { generateClient } from 'aws-amplify/data'
import type { Schema } from '../amplify/data/resource'
const client = generateClient<Schema>()
const sub = client.subscriptions.receive()
.subscribe({
next: event => {
console.log(event)
}
}
)

You can try publishing an event using the custom mutation to test the real-time subscription.

client.mutations.publish({
channelName: "world",
content: "My first message!"
})

Your subscription event should be received and logs the payload into your app's developer console. Unsubscribe your subscription to disconnect using the unsubscribe() function.

sub.unsubscribe()

(Optionally) Add server-side subscription filters

You can add subscription filters by adding arguments to the custom subscriptions.

If you want to customize the filters, modify the subscription handler. For this example, we'll allow a customer to pass in a namePrefix parameter that allows the end users to only receive channel events in channels that start with the namePrefix.

amplify/data/resource.ts
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
const schema = a.schema({
Channel: a.model({
name: a.string(),
}).authorization(allow => [allow.publicApiKey()]),
Message: a.customType({
content: a.string().required(),
channelName: a.string().required()
}),
publish: a.mutation()
.arguments({
channelName: a.string().required(),
content: a.string().required()
})
.returns(a.ref('Message'))
.handler(a.handler.custom({ entry: './publish.js' }))
.authorization(allow => [allow.publicApiKey()]),
receive: a.subscription()
.for(a.ref('publish'))
.handler(a.handler.custom({entry: './receive.js'}))
.authorization(allow => [allow.publicApiKey()])
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema
});

In your handler, you can set custom subscription filters based on arguments passed into the custom subscription. For this example, create a new receive.js file alongside the amplify/data/resource.ts file:

import { util, extensions } from "@aws-appsync/utils"
// Subscription handlers must return a `null` payload on the request
export function request() { return { payload: null } }
/**
* @param {import('@aws-appsync/utils').Context} ctx
*/
export function response(ctx) {
const filter = {
channelName: {
beginsWith: ctx.args.namePrefix
}
}
extensions.setSubscriptionFilter(util.transform.toSubscriptionFilter(filter))
return null
}