Using a DDL (Dynamic dropdown list) operation

You will now use the DDL operation you built within another connector operation get_movies_by_genre


  1. You have followed the walkthough on Building a DDL operation

Testing 3rd party endpoints

You will need the Discover Movies endpoint from TMDB API to build get_movies_by_genre operation.

The endpoint needs comma separated list of genre id. You can obtain id's from the response of Movie Genres endpoint you used to build the DDL.

You can pass those ids to Discover Movies endpoint as shown below:


Upon hititng Try it! you will get an array of movies which belong to the Genres specified.


Now with the information on inputs and outputs for the endpoint, you can initiate development for the operation get_movies_by_genre which will use the ddl operation list_movie_genres to pass the id's of the genres

Add operation

Add a new operation using:

tray-cdk connector add-operation get_movies_by_genre http

where list_movie_genres_ddl is the operation name and http is the operation type.

This would add a new folder under src with the following files:


Now we are ready to modify these files to build our operation.


The input object will include genre and it's type will be number (as that's the output value of the DDL).

Here's the complete input.ts for your reference:

export type GetMoviesByGenreInput = {
   * @title genre
   * @lookupOperation list_movie_genres_ddl
   * @lookupInput {}
   * @lookupAuthRequired true
  genre: number,

Here JSDoc annotation is used to indicate that this field is a dropdown.

@title - display value of this field on the properties panel. @lookupOperation - DDL operation name whoc response will feed options for the dropdown @lookupInput - Inputs required for making the call to the DDL operation. This is blank here as our DDL operation doesn't required any inputs. @lookupAuthRequired - If the DDL operation needs the auth to make the API call. In most cases this will be true as you are making calls to 3rd party APIs.


For building the output schema we can use the JSON output from the API Try it step.

  "page": 1,
  "results": [
      "adult": false,
      "backdrop_path": "/rMvPXy8PUjj1o8o1pzgQbdNCsvj.jpg",
      "genre_ids": [
      "id": 299054,
      "original_language": "en",
      "original_title": "Expend4bles",
      "overview": "Armed with every weapon they can get their hands on and the skills to use them, The Expendables are the world’s last line of defense and the team that gets called when all other options are off the table. But new team members with new styles and tactics are going to give “new blood” a whole new meaning.",
      "popularity": 687.684,
      "poster_path": "/iwsMu0ehRPbtaSxqiaUDQB9qMWT.jpg",
      "release_date": "2023-09-15",
      "title": "Expend4bles",
      "video": false,
      "vote_average": 6.4,
      "vote_count": 835
    ...Array of movie objects

Notice, this is same as the response for get_top_rated_movies. Hence, we can copy over the same outputs.ts to this operation. Here is the full Output.ts file for reference:

//movie object
export type GetTopRatedMoviesObject = {
  adult: boolean,
  backdrop_path: string,
  genre_ids: number[],
  id: number,
  original_language: string,
  original_title: string,
  overview: string,
  popularity: number,
  poster_path: string,
  release_date: string,
  title: string,
  video: boolean,
  vote_average: number,
  vote_count: number,

export type GetTopRatedMoviesOutput = {
  page: number,
  results: GetTopRatedMoviesObject[], //array of movie objects
  total_pages: number,
  total_results: number,


This file will contain your main function that defines the operation.

Perform the following steps on this file:

  1. Replace the URL inside http.get() function with /3/discover/movie
  2. Remove the line .addPathParameter('id', from the handleRequest function chain as our request doesn't need a URI parameter.
  3. Add .addQueryString("with_genres", input.genre.toString()) to the function chain. This will pass genre as a queryParameter with the API call.
  4. The endpoint needs the bearer <token> to be passed as an Authorization header to the operation.

Since we are using a globalConfig, We don't have to pass the auth for the request separately.

Alternatively if you wish to override the globalConfig, and want to add a bearer token with this request, you can simply add .withBearerToken(ctx.auth!.user.access_token) to the function chain.


Notice the ! sign in ctx.auth!.user.access_token. This tells the Typescript compiler that ctx.auth is non-null. Read more about Non-null assertion operator here.

Here's the complete handler.ts code for your reference:

import { OperationHandlerSetup } from "@trayio/cdk-dsl/connector/operation/OperationHandlerSetup";
import { AadiTmdbAuth } from "../AadiTmdbAuth";
import { GetMoviesByGenreInput } from "./input";
import { GetMoviesByGenreOutput } from "./output";
import { globalConfigHttp } from "../GlobalConfig";

export const getMoviesByGenreHandler =
  OperationHandlerSetup.configureHandler<AadiTmdbAuth, GetMoviesByGenreInput, GetMoviesByGenreOutput>
    ((handler) =>
      handler.withGlobalConfiguration(globalConfigHttp).usingHttp((http) =>
          .handleRequest((ctx, input, request) =>
              .addQueryString("with_genres", input.genre.toString())
          .handleResponse((ctx, input, response) => response.parseWithBodyAsJson())


A simple test for this endpoint could be to check the total number of movies returned in the response.

As per the documentaion, the endpoint should return 20 movie objects per page.

We can test this with:


Here's the complete handler.test.ts code for your reference:

import { OperationHandlerTestSetup } from "@trayio/cdk-dsl/connector/operation/OperationHandlerTest";
import { OperationHandlerResult } from "@trayio/cdk-dsl/connector/operation/OperationHandler";
import { getMoviesByGenreHandler } from "./handler";
import "@trayio/cdk-runtime/connector/operation/OperationHandlerTestRunner";

  (handlerTest) =>
      .testCase("should return 20 movies", (testCase) =>
          .when(() => ({ genre: 28 })) //dummy genre value. 28 is the genre code for Action movies
          .then(({ output }) => {
            const outputValue =

Test the operation

You can test the operation now by:

tray-cdk connector test [OPERATION_NAME]

in our case this is, tray-cdk connector test get_movies_by_genre

> aadi-tmdb@1.0.0 test
> jest --config ./jest.config.js "get_movies_by_genre"

 PASS  src/get_movies_by_genre/handler.test.ts
  Operation get_movies_by_genre Test
    ✓ should return 20 results (83 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.154 s, estimated 2 s
Ran all test suites matching /get_movies_by_genre/i.

Re-deploy the connector


We are assuming you have deployed the connector once by following the Deployment section from Quickstart. If not, please go back and do it first.

Double check if TRAY_API_URL and TRAY_API_TOKEN are present in the environment variables.

You can check these by doing echo "$TRAY_API_URL" or echo "$TRAY_API_TOKEN" respectively.


If you used the session token as TRAY_API_TOKEN, remember session tokens are valid for 2 days so you may need to update the value of this by running export TRAY_API_TOKEN=<YOUR_SESSION_TOKEN> on the terminal.

If you used MASTER_TOKEN then no need to update this unless your master token was deleted and new was generated.

Now you can redeploy the connector using:

tray-cdk deployment create

Use the connector on Tray UI

Wait for a couple of minutes for the connector to show on the Tray UI with the new GetMoviesByGenre operation.

Now you can test this operation on the UI by selecting a genre and adding an auth.