Create a Topic

Learn how to create a new topic and submit your first message on Hedera testnet using the JavaScript, Java, or Go SDK. A topic on the Hedera Consensus Service (HCS) is like a public channel: anyone who knows the topic ID can publish timestamped messages, and anyone can subscribe to the stream from a mirror node.


Prerequisites

  • A Hedera testnet operator account ID and DER-encoded private key (from the Quickstart)y (from the Quickstart).

  • A small amount of testnet HBAR (ℏ) to cover the fees

    • Topic creation: ≈ $0.01

    • Each message: < $0.001

Note

You can always check the "Code Check" section at the bottom of each page to view the entire code if you run into issues. You can also post your issue to the respective SDK channel in our Discord community here.


Install the SDK

Open your terminal and create a directory hedera-examples directory. Then change into the newly created directory:

mkdir hedera-examples && cd hedera-examples

Initialize a node.js project in this new directory:

npm init -y

Ensure you have Node.js v18 or later installed on your machine. Then, install the JavaScript SDK.

npm install --save @hashgraph/sdk

Update your package.json file to enable ES6 modules and configure the project:

{
  "name": "hedera-examples",
  "version": "1.0.0",
  "type": "module",
  "main": "createTopicDemo.js",
  "scripts": {
    "start": "node createTopicDemo.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@hashgraph/sdk": "^2.69.0"
  }
}

Create a createTopicDemo.js file and add the following imports:

import {
  Client,
  TopicCreateTransaction,
  TopicMessageSubmitTransaction
} from "@hashgraph/sdk";

Environment Variables

Set your operator credentials as environment variables:

export OPERATOR_ID="0.0.1234"
export OPERATOR_KEY="302e020100300506032b657004220420..."

Step 1: Initialize Hedera Client

Load your operator credentials from environment variables and configure your Hedera testnet client. The client manages your connection to the Hedera network and uses your operator account to sign transactions and pay fees.

// Load your operator credentials
const operatorId  = process.env.OPERATOR_ID;
const operatorKey = process.env.OPERATOR_KEY;

// Initialize your testnet client and set operator
const client = Client.forTestnet()
    .setOperator(operatorId, operatorKey);

Step 2: Create a new topic

TopicCreateTransaction builds and sends the transaction to register a new HCS topic with the provided memo, and once it reaches consensus you retrieve the transaction receipt to extract the new topicId.

Why messages?

Messages on HCS are consensus-timestamped and immutable. Once published, they become part of the permanent record that anyone can verify and subscribe to in real-time.

// Build and send the transaction
const txResponse = await new TopicCreateTransaction()
  .setTopicMemo("My first HCS topic")   // optional description
  .execute(client);

const receipt = await txResponse.getReceipt(client);
const topicId = receipt.topicId;

console.log(`Topic created: ${topicId.toString()}`);

What just happened?

  1. The SDK built a TopicCreateTransaction.

  2. Your operator key signed and paid the fee.

  3. Hedera nodes reached consensus and returned a unique topic ID (shard.realm.num).


Step 3: Submit Message to Topic

TopicMessageSubmitTransaction constructs and sends a transaction that submits your payload (string, bytes, or Uint8Array) as a message to a specified HCS topic. Once it reaches consensus, the message becomes part of that topic’s immutable record.

// Build & execute the message submission transaction
const message = "Hello, Hedera! \n";

const messageTransaction = new TopicMessageSubmitTransaction()
  .setTopicId(topicId)
  .setMessage(message);

await messageTransaction.execute(client);

console.log(`Message submitted to topic: ${message}\n`);

Note

Messages can be up to 1 KiB each. Larger payloads must be chunked automatically by the SDK or split manually


Step 4: Query Messages Using Mirror Node API

Use the Mirror Node REST API to verify your message was published to the topic. Mirror nodes provide free access to network data without transaction fees. Mirror nodes stream every consensus-timestamped message in order, letting your app react in real time.

API endpoint:

/api/v1/topics/{topicId}/messages

Replace the placeholder:

  • {topicId} - Your topic ID from the creation transaction

Why this endpoint?

This endpoint retrieves all messages published to a specific topic, ordered by consensus timestamp. It returns detailed information including message content, timestamp, and sequence number, making it ideal for verifying your message was published successfully.

Example URLs:

const mirrorNodeUrl = `https://testnet.mirrornode.hedera.com/api/v1/topics/${topicId}/messages`;

Complete Implementation:

// Wait for Mirror Node to populate data
console.log("Waiting for Mirror Node to update...\n");
await new Promise(resolve => setTimeout(resolve, 3000));

// query messages using Mirror Node
const mirrorNodeUrl = `https://testnet.mirrornode.hedera.com/api/v1/topics/${topicId}/messages`;

const response = await fetch(mirrorNodeUrl);
const data = await response.json();

if (data.messages && data.messages.length > 0) {
  const latestMessage = data.messages[data.messages.length - 1];
  const messageContent = Buffer.from(latestMessage.message, 'base64').toString('utf8');
  
  console.log(`Latest message: ${messageContent}\n`);
} else {
  console.log("No messages found yet in Mirror Node\n");
}

client.close();

✅ Code check

Before running your project, verify your code matches the complete example:

JavaScript
import {
  Client,
  TopicCreateTransaction,
  TopicMessageSubmitTransaction
} from "@hashgraph/sdk";

async function createTopicDemo() {
  // load your operator credentials
  const operatorId = process.env.OPERATOR_ID;
  const operatorKey = process.env.OPERATOR_KEY;

  // initialize the client for testnet
  const client = Client.forTestnet()
    .setOperator(operatorId, operatorKey);

  // build & execute the topic creation transaction
  const transaction = new TopicCreateTransaction()
    .setTopicMemo("My first HCS topic");

  const txResponse = await transaction.execute(client);
  const receipt = await txResponse.getReceipt(client);
  const topicId = receipt.topicId;

  console.log(`\nTopic created: ${topicId}\n`);

  // build & execute the message submission transaction
  const message = "Hello, Hedera! \n";

  const messageTransaction = new TopicMessageSubmitTransaction()
    .setTopicId(topicId)
    .setMessage(message);

  await messageTransaction.execute(client);

  console.log(`Message submitted to topic: ${message}\n`);

  // Wait for Mirror Node to populate data
  console.log("Waiting for Mirror Node to update...\n");
  await new Promise(resolve => setTimeout(resolve, 3000));

  // query messages using Mirror Node
  const mirrorNodeUrl = `https://testnet.mirrornode.hedera.com/api/v1/topics/${topicId}/messages`;

  const response = await fetch(mirrorNodeUrl);
  const data = await response.json();

  if (data.messages && data.messages.length > 0) {
    const latestMessage = data.messages[data.messages.length - 1];
    const messageContent = Buffer.from(latestMessage.message, 'base64').toString('utf8');
    
    console.log(`\nLatest message: ${messageContent}\n`);
  } else {
    console.log("No messages found yet in Mirror Node\n");
  }

  client.close();
}

createTopicDemo().catch(console.error);
Java
import com.hedera.hashgraph.sdk.*;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonArray;

public class CreateTopicDemo {
    public static void main(String[] args) throws Exception {
        // load your operator credentials
        String operatorId = System.getenv("OPERATOR_ID");
        String operatorKey = System.getenv("OPERATOR_KEY");

        // initialize the client for testnet
        Client client = Client.forTestnet()
            .setOperator(AccountId.fromString(operatorId), PrivateKey.fromString(operatorKey));

        // build & execute the topic creation transaction
        TopicCreateTransaction transaction = new TopicCreateTransaction()
            .setTopicMemo("My first HCS topic");

        TransactionResponse txResponse = transaction.execute(client);
        TransactionReceipt receipt = txResponse.getReceipt(client);
        TopicId topicId = receipt.topicId;

        System.out.println("Topic created: " + topicId + "\n");

        // build & execute the message submission transaction
        String message = "Hello, Hedera!";

        TopicMessageSubmitTransaction messageTransaction = new TopicMessageSubmitTransaction()
            .setTopicId(topicId)
            .setMessage(message);

        messageTransaction.execute(client);

        System.out.println("Message submitted to topic: " + message + "\n");

        // Wait for Mirror Node to populate data
        System.out.println("Waiting for Mirror Node to update...\n");
        Thread.sleep(3000);

        // query messages using Mirror Node
        String mirrorNodeUrl = "https://testnet.mirrornode.hedera.com/api/v1/topics/" + topicId + "/messages";

        HttpClient httpClient = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(mirrorNodeUrl))
            .build();

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        Gson gson = new Gson();
        JsonObject data = gson.fromJson(response.body(), JsonObject.class);

        if (data.has("messages") && data.getAsJsonArray("messages").size() > 0) {
            JsonArray messages = data.getAsJsonArray("messages");
            JsonObject latestMessage = messages.get(messages.size() - 1).getAsJsonObject();
            String encodedMessage = latestMessage.get("message").getAsString();
            String messageContent = new String(java.util.Base64.getDecoder().decode(encodedMessage));
            
            System.out.println("Latest message: " + messageContent + "\n");
        } else {
            System.out.println("No messages found yet in Mirror Node\n");
        }

        client.close();
    }
}
Go
package main

import (
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"

    "github.com/hiero-ledger/hiero-sdk-go/v2"
)

func main() {
    // load your operator credentials
    operatorId, err := hedera.AccountIDFromString(os.Getenv("OPERATOR_ID"))
    if err != nil {
        panic(err)
    }

    operatorKey, err := hedera.PrivateKeyFromString(os.Getenv("OPERATOR_KEY"))
    if err != nil {
        panic(err)
    }

    // initialize the client for testnet
    client := hedera.ClientForTestnet()
    client.SetOperator(operatorId, operatorKey)

    // build & execute the topic creation transaction
    transaction := hedera.NewTopicCreateTransaction().
        SetTopicMemo("My first HCS topic")

    txResponse, err := transaction.Execute(client)
    if err != nil {
        panic(err)
    }

    receipt, err := txResponse.GetReceipt(client)
    if err != nil {
        panic(err)
    }

    topicID := *receipt.TopicID

    fmt.Printf("Topic created: %s\n\n", topicID.String())

    // build & execute the message submission transaction
    message := "Hello, Hedera!"

    messageTransaction := hedera.NewTopicMessageSubmitTransaction().
        SetTopicID(topicID).
        SetMessage([]byte(message))

    _, err = messageTransaction.Execute(client)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Message submitted to topic: %s\n\n", message)

    // Wait for Mirror Node to populate data
    fmt.Println("Waiting for Mirror Node to update...\n")
    time.Sleep(3 * time.Second)

    // query messages using Mirror Node
    mirrorNodeUrl := "https://testnet.mirrornode.hedera.com/api/v1/topics/" + topicID.String() + "/messages"

    resp, err := http.Get(mirrorNodeUrl)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    var data struct {
        Messages []struct {
            Message string `json:"message"`
        } `json:"messages"`
    }

    err = json.Unmarshal(body, &data)
    if err != nil {
        panic(err)
    }

    if len(data.Messages) > 0 {
        latestMessage := data.Messages[len(data.Messages)-1]
        decodedMessage, _ := base64.StdEncoding.DecodeString(latestMessage.Message)
        
        fmt.Printf("Latest message: %s\n\n", string(decodedMessage))
    } else {
        fmt.Println("No messages found yet in Mirror Node\n")
    }

    client.Close(nil)
}

Run Your Project

Ensure your environment variables are set:

export OPERATOR_ID="0.0.1234"
export OPERATOR_KEY="302e020100300506032b657004220420..."
node createTopicDemo.js

Expected sample output:

Topic created: 0.0.1234567

Message submitted: Hello, Hedera!

Waiting for Mirror Node to update...

Latest message: Hello, Hedera!

‼️ Troubleshooting

Common ERROR messages and solutions ⬇️
Error message
Likely cause
Fix

INSUFFICIENT_PAYER_BALANCE

Not enough HBAR for topic creation fee

Top up your operator account on the testnet faucet

INVALID_SIGNATURE

Operator key doesn't match operator account

Verify OPERATOR_KEY matches your OPERATOR_ID

INVALID_ACCOUNT_ID

Malformed account ID in environment variables

Verify OPERATOR_ID format is 0.0.1234

INVALID_PRIVATE_KEY

Malformed private key in environment variables

Verify OPERATOR_KEY is a valid DER-encoded private key string

INVALID_TOPIC_ID

Topic ID not found or malformed

Ensure topic was created successfully and ID is correct

No messages found yet

Mirror Node sync delay

Wait a few more seconds and try again - this is normal


What Just Happened?

  1. The SDK built a TopicCreateTransaction and signed it with your operator key.

  2. A consensus node validated the signature and charged the topic creation fee.

  3. After network consensus, a unique topic ID was assigned and returned in the receipt.

  4. Your message was submitted to the topic and became part of the immutable record.

  5. The Mirror Node API confirmed your message was published with consensus timestamp


Next steps

  • Subscribe to the topic from your backend or front-end to process messages as they come in

  • Encrypt messages if you need privacy before publishing

  • Explore more examples in the SDK repos (JavaScript, Java, Go)


🎉 Great work! You have created a new topic and broadcasted your first message on Hedera testnet. Keep building!

Additional Resources

Last updated

Was this helpful?