Skip to main content


On this page, you can earn points for interacting with the Ceramic Network. Let's walk you through the potential actions you can perform while you learn!

Read Query

Use the GraphiQL modules below to view existing data

Not Connected

Sandbox Mutation Query

Use the GraphiQL modules within the "Writing Data" section to create new data

Not Connected

Sandbox Extension Mutation Query

Clone the reposority found in the "Writing Data" section to create new data

Not Connected

Before we dive in, let's get you familiar with the GraphiQL interface. Start by pressing the pink play ▶️ icon in the component below:


You've performed your first query on a live ComposeDB node! Below are the data models the query provided for you is referencing:

type Posts
@createModel(accountRelation: LIST, description: "A simple Post")
@createIndex(fields: [{ path: "created" }])
@createIndex(fields: [{ path: "edited" }])
@createIndex(fields: [{ path: "tag" }]) {
author: DID! @documentAccount
body: String! @string(minLength: 1, maxLength: 100)
tag: String! @string(minLength: 1, maxLength: 100)
edited: DateTime
created: DateTime!
profileId: StreamID! @documentReference(model: "BasicProfile")
profile: BasicProfile! @relationDocument(property: "profileId")

The query you performed essentially translates to "give me the first 10 indexed instances of Posts and show me the id, body, tag, created, and author id field values from that model."

Easy, right? Let's introduce another model into the mix:

type BasicProfile @createModel(accountRelation: SINGLE, description: "A basic Profile") {
author: DID! @documentAccount
name: String! @string(minLength: 3, maxLength: 100)
username: String! @string(minLength: 5, maxLength: 255)
description: String @string(minLength: 3, maxLength: 100)
gender: String @string(minLength: 3, maxLength: 100)
emoji: String @string(minLength: 1, maxLength: 2)

Here's a diagram to help you better visualize the relationships between a given user and instances of these two schemas:

sandbox diagram

Continue into the next section to experiment with queries that utilize this BasicProfile model.

Reading Profiles

Use the example queries below to access data related to the BasicProfile model referenced above. Comments have been left for you above each query to help you understand what the query is requesting.


You may have noticed that the fourth tab labeled "WithComments" references a new model we havent discussed yet. Below is the model definition for your reference:

type Comments
@createModel(accountRelation: LIST, description: "A comment on a post")
@createIndex(fields: [{ path: "created" }])
@createIndex(fields: [{ path: "edited" }]) {
comment: String! @string(minLength: 1, maxLength: 100)
edited: DateTime
created: DateTime!
profileId: StreamID! @documentReference(model: "BasicProfile")
profile: BasicProfile! @relationDocument(property: "profileId")
postId: StreamID! @documentReference(model: "Posts")
post: Posts! @relationDocument(property: "postId")

We've also defined a field within our BasicProfile model that exposes the relationship between a BasicProfile instance and all Posts that they've created:

type BasicProfile @loadModel(id: "$PROFILE_ID") {
posts: [Posts] @relationFrom(model: "Posts", property: "profileId")

This is why you're able to access the first 5 Posts created by each BasicProfile in the "WithPosts" tab.

Finally, we've defined a similar field within our Posts definition that allows you to access all Comments corresponding to that post:

type Posts @loadModel(id: "$POSTS_ID") {
responses: [Comments] @relationFrom(model: "Comments", property: "postId")

You'll notice that the two sections below also leverage those features by calling on those relationships.

Reading Posts

Use the example queries below to access data related to the Posts model:


As mentioned in the previous section, several of the queries left for you ask for items in the responses field of Posts (which yields Comments corresponding to that Posts instance).

Reading Comments

Finally, use the example queries below to access data related to the Comments model:


Great work! You now know how to query based on multiple relationship types, perform filtering and ordering, and even combine filters. Hopefully you've also created a few custom queries of your own to test out.

Now that you're familiar with reading data, let's learn how to perform mutations.

Writing Data

Data Models

The following sandbox section will allow you to create Ceramic documents using a different type of query called mutations. For this section, you will be using a different model definition. These have been included for your reference below and have already been deployed onto the node this sandbox uses.

enum Proficiency {

type Language {
JavaScript: Proficiency
Python: Proficiency
Rust: Proficiency
Java: Proficiency
Swift: Proficiency
Go: Proficiency
Cpp: Proficiency
Scala: Proficiency
WebAssembly: Proficiency
Solidity: Proficiency
Other: Proficiency

type CeramicDev
accountRelation: SET
accountRelationFields: ["context"]
description: "A Ceramic developer") {
developer: DID! @documentAccount
context: String! @string(maxLength: 100)
languages: Language!
attestations: [AttestToDev] @relationFrom(model: "AttestToDev", property: "attestedProfileId")

type AttestToDev @createModel(
accountRelation: SET
accountRelationFields: ["attestedProfileId"]
description: "Signals if user attests to another developer profile") {
attester: DID! @documentAccount
attestedProfileId: StreamID! @documentReference(model: "CeramicDev")
profile: CeramicDev! @relationDocument(property: "attestedProfileId")
signal: Boolean!

You will use this model to self-describe your own development skills, and will therefore require you to alter the given examples to meet your actual abilities.

You'll notice several unique qualities within the definitions above:

Embedded "Language" Type

Unlike the definitions used above in the read section, CeramicDev makes use of an embedded type called Language, which allows developers the ability to use these definitions in their data modeling as if it were a scalar. It's important to note that embedded types also introduce some restrictions - for example, developers impose filtering and ordering based on embedded types.

Proficiency Enum

Our embedded Language type uses an enum called Proficiency that restricts the possible values each key within Language can be assigned to. In our use case, we implemented a simple paradigm that uses a scale of 4 proficiency "levels."

"SET" Account Relation

Most importantly, you'll see that SET is used to define the relation between the authoring account and the model instance document. Unlike LIST (which allows an infinite number of model instance documents to be associated with a given account), SET lets developers restrict the number of instance documents to 1 based on the value of a subfield.

A simple way to understand this is to think about reviews on an eCommerce platform. If a customer has purchased a product, the platform should ensure that the customer can only leave 1 review for the product they have purchased.

In CeramicDev,


You will first have to self-authenticate on the Ceramic node this sandbox environment uses. This is required for making mutation queries, whereas you do not have to be authenticated on a given node to read its data. In order to do so, you will need the MetaMask wallet browser extension installed.

Once you're ready, go ahead and press the Authenticate on Ceramic button below, and sign the messages within the MetaMask popups.

Your browser wallet should create a prompt for you that looks something like this:

Sign in prompt

Notice how there's 1 model listed under "Resources" in the prompt - the model listed here is the StreamID of the CeramicDev model definition shown above that has already been deployed onto Ceramic. By signing this prompt, you are creating a temporary session that allows the current application to create edits on your behalf only to the resources you've granted access to (in this case, our CeramicDev schema).

Creating a Record

Now that you're authenticated, you'll be able to create a CeramicDev profile of your own! Be sure to adjust the dummy queries below to meet your unique skill set. You can edit the languages variable and the corresponding proficiency fields for each language that represents your own expertise.

For example, if you notice that a language like Cpp is missing from your mutation and you want to signal that you have Intermediate experience, you can create a new line withing your languages object and add Cpp: Intermediate.


Developer Feed

Below you'll see the most recent 8 CeramicDev records in addition to all verifications that have been issued in relation to that CeramicDev profile (these have been filtered to ensure each verification is from a unique profile):


Write Data in a Local Environment

Notice how some of the profiles have obtained verifications? The ability to create an AttestToDev instance using this sandbox has been intentionally omitted from the runtime definition used on this page. We've done so to encourage developers to complete this sandbox module by following the steps below that allow you to verify your profile in a local environment.

To get started, clone the extension repository, install your dependencies, and run the local application:

git clone
cd sandbox-extension
npm install
npm run dev

The operations above will run the extension application in developer mode - go ahead and navigate to http://localhost:3000 in your browser to start interacting with the application.

Once you have the extension running locally and visible in your browser, you'll be able to sign in using MetaMask. Be sure to use the same address and network you used when authenticating yourself on this page.

Click the Sign in with MetaMask button in the upper right-hand corner as the screenshot shows below to get started.

Sign in prompt

Once authenticated, you'll be able to verify your own profile as well as others. If you've signed in using the same account and network as the one you used on the sandbox page, you should see the profile you just created appended to the right:

before verifying

Notice how your profile does not currently have any unique verifications. You can go ahead and verify by clicking the Verify Profile button for that item. You should now see something like this:

before verifying

You can also verify profiles made by other developers that are appended to the left of your screen.

Finally, once you've verified your profile, return back to this page. You should now see your verification(s) counted for each unique verification you issued in your local environment back in the Developer Feed

You've done it! You now know how to perform successful read and write queries on a live ComposeDB node!

Next Steps

To begin building with ComposeDB in your local environment using the same data models you just queried, get started with setting up your environment.