# Creating Miniapps on Seam

<div align="center"><img src="https://img.shields.io/github/commit-activity/m/seam-xyz/Block-SDK" alt=""> <img src="https://img.shields.io/github/stars/seam-xyz/Block-SDK?style=social" alt=""></div>

<div data-full-width="true"><figure><img src="https://github.com/seam-xyz/Miniapp-Builder/assets/7350670/1929270a-3b8e-46b2-ab60-824b248b9bcb" alt="" width="563"><figcaption><p>Miniapps are the future of sharing on social media</p></figcaption></figure></div>

## Create apps & games for a gen-z social network!

[Seam](https://www.seam.so) is a social network that allows users to build and sell open-source miniapps. It provides a platform for online creatives to showcase their work, curate inspiration, and collaborate with peers.

Accepted miniapps are live on [seam.so](https://www.seam.so) and the [Seam iOS app](https://apps.apple.com/us/app/seam-social/id6473547569). You'll have your code in the appstore!

#### **Why develop a miniapp as your personal project?**

* **Quick Development:** Build your project in an hour or less, without the hassle of hosting frameworks.&#x20;
* **Remix Open Source Code:** Learn from and customize existing miniapps.&#x20;
* **Free Databases:** Seam takes care of all your data storage needs.&#x20;
* **Find Users:** Go live to everyone on Seam.&#x20;
* **Earn Rewards:** Get paid in Seam Points each time your miniapp is unlocked.&#x20;
* **Join a Growing Ecosystem:** Be an early adopter to the future of social networks.

## What are miniapps?

The core piece of sharing on Seam is the **miniapp**. Miniapps are tiny playgrounds, allowing you to create content. When you’re done, you make a post. For example, the Pixel Art miniapp allows you to become a pixel artist, and then share the final masterpiece as a post.

Miniapps are React components coded in Typescript, styled with Tailwind. This means that you can use all the existing React code on the internet to get started creating your miniapp — as a bonus, ChatGPT and other AI assistants are really good at writing React code :)

<figure><img src="https://github.com/seam-xyz/Miniapp-Builder/assets/7350670/9ca67061-35ac-4a0b-a6a2-bebd5c66930f" alt="" width="563"><figcaption><p>Miniapps have a creation experience, and a consumption experience in the feed.</p></figcaption></figure>

Read on to get started building Seam miniapps!


# Quickstart

Step-by-step guide on how to create your own Seam miniapp

{% embed url="<https://youtu.be/oPiLc6U1oIA?si=mw2DICgrm72LQO6L>" %}
Tutorial for getting started creating a Seam miniapp
{% endembed %}

### Create a Seam Account

Before creating a miniapp, you should join Seam! Creating a Seam account allows you to claim a username and profile that will be used as the author of the miniapp. More importantly though, it allows you to join a community of other builders, designers, and curators all building the future of social networking together.

Either join on web at [seam.so](https://www.seam.so), or [download our iOS app from the Apple appstore](https://apps.apple.com/us/app/seam-social/id6473547569).

### Setup your development environment

If you're new to programming in general and want to get setup for the first time, head to the [Local Environment Setup](/get-started/local-environment-setup) section to learn more. Otherwise, if you have a favorite code editor and a local Node environment, you're good to go.

### Fork the Miniapp Builder Repository

The next step is to get the miniapp testing code running on your machine. All Seam miniapp code is open source and can be found in our [GitHub repository here](https://github.com/seam-xyz/Miniapp-Builder).&#x20;

First, [fork the Seam Miniapp github repository](https://github.com/seam-xyz/Miniapp-Builder/fork) to your own account, so that you'll be able to make a Pull Request later.

Then, clone your fork with the GitHub CLI tool:

```
gh repo clone [[your github username here]]/Miniapp-Builder
```

Next, in your terminal, install all the dependencies with:

<pre><code><strong>$ cd Miniapp-Builder
</strong>$ yarn install
</code></pre>

Finally, get started with an example home feed of the Seam miniapp builder by running:

<pre><code><strong>$ yarn start
</strong></code></pre>

The Seam miniapp testing harness will show up in your browser at <http://localhost:3000/>. This is a fake feed that you can post into to try all the different miniapps that exist on Seam, and to make sure your new one is working as expected.

### Run the ✨ Seam Magic ✨ script

Now it's time to make some Seam Magic! Starting a new Seam miniapp is simple. In your Miniapp Builder repository terminal window, run the Seam Magic script and it will handle the rest:

<pre><code><strong>$ sh ./seam-magic.sh
</strong></code></pre>

You'll get asked three questions:

```
[1/3] What's your Seam username?
```

Entering your username allows Seam to give you Seam Points when someone unlocks your miniapp, and also give you credit as the author in the composer.

```
[2/3] What should your app be called?
```

This is the title of your miniapp that users will see when they unlock it from the composer. It also will be used to autogenerate code files for you. Some tips:

1. Good miniapp names are short (we trim to the first word).
2. The script autocapitalizes the first letter of the name, and the rest will be lowercased.
3. Miniapp needs to be unique -- so if you have another Image or Video miniapp, make sure to differentiate it!

```
[3/3] What's the description of your app?
```

This is the short (100 characters, max) description of what your miniapp allows users to post. Here are some examples from our existing miniapps:

* "Upload an image and turn it into a sliding puzzle!"
* "Draw on a whiteboard!"
* Create works of art in the style of Piet Mondrian"

### Next Steps

Once you have your miniapp files all set up, it's time to code your [Composer Component](/miniapp-creation/composer-component).


# Local Environment Setup

To get started building on Seam, the only requirement is a Node JS environment. Seam miniapps are written in [Typescript](https://www.typescriptlang.org/) and are [React](https://react.dev/) components. Read on to get setup with the programming tools on your own computer.

### Node, NPM & Yarn

Node.js is the runtime environment that most modern JavaScript applications use to run their code.

Like all JavaScript projects, Seam has dependencies on 3rd party packages which provide additional functionality for the miniapps and also getting the web app to work as intended. To install and manage these dependencies, a package manager is required.

At Seam, we prefer [Yarn](https://yarnpkg.com/getting-started) over [NPM](https://www.npmjs.com/). In our documentation, you'll often see `yarn install` or `yarn start`. This is because Yarn is faster and more secure than the Node Package Manager (NPM).

Of course, a code editor is also necessary. We recommend [Visual Studio Code](https://code.visualstudio.com/) which is a free, open source text editor made by Microsoft.


# Composer Component

Allowing the user to create their post

{% embed url="<https://www.youtube.com/watch?v=zv_h8QbiY4o>" %}
Video walkthrough of creating the Composer Component
{% endembed %}

The first step to creating a miniapp is to define the composer component, which is what displays in the post composer and allows a user to create a post using your miniapp.

After running the `./seam-magic.sh` script in the quickstart, you'll be given a composer component that looks like this:

```
export const YourMiniappComposerComponent = ({ model, done }: ComposerComponentProps) => {
  return (
    <div>
      <h1>Hi, I'm in the composer!</h1>
      <button onClick={() => { done(model) }}> Post </button>
    </div>
  );
}
```

The job of the Composer Component is to store data in the `BlockModel`. Depending on what type of miniapp you are creating, you can create input fields here or even entire full gaming experiences. As the user makes selections, earns a highscore, or composes their song, add the relevant strings to the model. Then, call the done function with your updated model.

### Storing User Data

All data gets stored in the `BlockModel` which is a key-value store which holds strings. Seam stores the model in our database for you, so you don't have to worry about backends, authentication, or users. Each model is unique per post, and holds all the data necessary to render a post in the feed.

Storing a string is as easy as adding it to the model's `data` property:

```
model.data['text'] = "New User Text Here!"
```

Make sure not to store any data in here that is too large, for performance reasons. If you want to store images, videos, or other large files, you can use our [FileUploader](/miniapp-creation/uploading-images-videos-and-files) component, and store the URL as a string in the model.

**Storing Numbers**

To store numbers, you can use the Javascript function `toString`, like so:

```
const highscore: number = 10;
model.data['highscore'] = highscore.toString();
```

**Storing Arrays**

The model only allows strings, so how do you store more complex data types, like arrays? For that, you can use the `JSON.stringify()` function, like so:

```
const userPreferences = ['apple', 'orange'];
model.data['userPreferences'] = JSON.stringify(userPreferences)
```

Again, make sure to not store arrays that are too large in the data model -- that'll cause your posts to render slowly in the feed!

### Posting

Once the user is done in your miniapp, call the done function with the updated model. This will automatically advance the composer to the preview step. For example from the Camera miniapp:

```
const onFinalize = (photoUrl: string) => {
  model.data.photoUrl = photoUrl;
  done(model);
};
```

### Next Steps

Once you have a Composer Component that takes in user choices, saves them to the model, and calls the done function, run `yarn start` to see your latest changes live in the example composer. As always, check out other open source miniapps to learn how other people have constructed their miniapps.

Next, it's time to create the Feed Component!


# Feed Component

Allowing the user to view a post in the feed

{% embed url="<https://www.youtube.com/watch?v=qkqKN8Pmc4M>" %}
Coding in React to make a Seam Miniapp Feed Component
{% endembed %}

The second step to creating a miniapp is to define the feed component, which is what displays in the feed of Seam.

After running the `./seam-magic.sh` script in the quickstart, you'll be given a feed component that looks like this:

```
export const YourAppFeedComponent = ({ model }: FeedComponentProps) => {
  return <h1>Hi, I'm in the feed!</h1>;
}
```

The job of the Feed Component is to read the data out of the model, and use it to render a post in the feed.

### Fetching User Data

Just as you stored data in the composer component, in the feed component you'll need to get the strings you stored out. Getting data out of the model is as easy as:

```
const photoURL = model.data['photoURL']
```

Make sure that the key that you're looking at is the same as the one you used in your Composer Component.

**Retrieving Numbers**

To get numbers out of the model, you can use the Javascript function `parseInt`, like so:

```
const highscore: number = parseInt(model.data['highscore'])
```

**Retrieving Arrays**

Once you've stored your array in the model using `stringify`, you can get it back out again into an array by using `JSON.parse`, like so:

```
let urls = model.data['urls'] ? JSON.parse(model.data['urls']) : [];
```

It's important to check if the model data does have data in it to avoid crashing from the JSON parsing an undefined variable.

### Rendering User Data

Then, once you have successfully retrieved your string data, you can render a component's UI. For example, here's a simple miniapp that just renders an image that it is given:

```
export const ImageFeedComponent = ({ model }: FeedComponentProps) => {
  let photoURL = model.data['photoURL']
  return (
    <div style={{ display: 'block', width: '100%' }}>
      <img src={photoURL} style={{ width: '100%', height: 'auto' }} />
    </div>
  );
}
```

### Updating User Data

If your miniapp requires changing the data stored in a post after it has been posted to the feed (for example: the Poll Miniapp), use the `update` function.

Not unlike the done function, `update` is designed to take a single argument, your updated post data, of the following type: `({ [key: string]: string })`

In the example of the Poll Miniapp, when a user votes on a poll, the new vote totals need to be stored and reflected across Seam. The `update` function handles this by interacting with Seam's backend.

```
const handleVote = async (optionKey: string) => {
  if (hasVoted) return; // Prevent multiple votes

  const updatedVotes = { ...votes, [optionKey]: votes[optionKey] + 1 };
  setVotes(updatedVotes); // Optimistic update
  setTotalVotes(totalVotes + 1);
  setSelectedOption(optionKey);
  setHasVoted(true);

  try {
    // Prepare the updated data for the backend
    const updatedData = { ...model.data };
    updatedData[`${optionKey}Votes`] = updatedVotes[optionKey].toString();

    // Call the update function to save the new vote counts
    if (update) {
      await update(updatedData);
    }
  } catch (error) {
    console.error('Failed to update votes:', error);
  }
};
```

In the above example, after a Seam user casts their vote, the `update` function is called with the updatedData containing the user's vote. The function call will update that post's data on Seam's backend.

Poll Miniapp postData before calling the update function:

```
"data": {
  "question": "a or b",
  "option1": "a",
  "option1Votes": “0”,
  "option2": "b",
  "option2Votes": "0"
},
```

After:

```
"data": {
  "question": "a or b",
  "option1": "a",
  "option1Votes": “1”,
  "option2": "b",
  "option2Votes": "0"
},
```

### Optimistic UI Updates

It is generally advised to update the local state before calling the `update` function. This will provide immediate optimistic feedback to the user while the backend update is processed.


# Supporting Mobile

All miniapps on Seam run on all devices, both phones and on web on desktop computers. Therefore, as a miniapp developer, you'll need to make sure that you're happy with how your miniapp works on both large devices and small devices. By default, the miniapp testing harness that you've been using so far as been the desktop composer -- but there is an easy way to change the dimensions to make sure your miniapp works for phones, too.

### Testing on Mobile

The best way to test for mobile screen sizes is by using the dev tools built into your web browser. For example, in Chrome, you'll want to use [Device Mode](https://developer.chrome.com/docs/devtools/device-mode).

{% embed url="<https://developer.chrome.com/static/docs/devtools/device-mode/image/the-dimensions-list-c94d72e725e49_1920.png>" %}
Using Chrome devtools, you can try all the different device types
{% endembed %}

### Requirements

Make sure you like your miniapp at the following widths:

* Mobile Small: 320px
* Mobile Medium: 375px
* Mobile Large: 425px


# Uploading Images, Videos, and Files

If your miniapp involves allowing users to upload files, Seam provides a [Firebase](https://firebase.google.com/docs/emulator-suite) storage bucket for our miniapp file upload and storage. This allows you to upload any file (image, video, sounds, 3d images) and not worry about hosting your own backend. Additionally, you can use this simulated testing enviroment when you build you miniapp, without needing to upload to a real server.

#### Getting started: Setting up Firebase locally

To get started uploading files, you'll need to run a local server to simulate the process of uploading and downloading files. Open up a new terminal at your Miniapp Builder SDK directory and run the following command:

```
npx firebase emulators:start
```

Then, you can see a sample file upload bucket at `http://127.0.0.1:4000/storage/demo-seam-miniapp-builder/files`.

#### Uploading Images

To upload images in your miniapp, you can use the `FileUploadComponent` provided to you by Seam, like so:

```
import FileUploadComponent from './utils/FileUploadComponent';

const handleUpdate = (uploadedUrls: string[]) => {
    setPreviewUrls(uploadedUrls);
};

<FileUploadComponent
    fileTypes="image/*"
    label={previewUrls.length > 0 ? "Upload More Images" : "Upload Images"}
    onUpdate={handleUpdate}
    multiple={true}
    maxFiles={10}
/>
```

This gives you a button that when clicked allows the user to pick photos from their camera roll. Check out the `ImageComposerComponent` for a full example.

#### Uploading Videos

To upload videos in your miniapp, you can use the same `FileUploadComponent`, but specify a fileType of video, like so:

```
import FileUploadComponent from './utils/FileUploadComponent';

const [uploadedUrl, setUploadedUrl] = useState(model.data['url'] || "");

const handleUpdate = (urls: string[]) => {
    if (urls.length > 0) {
      setUploadedUrl(urls[0]);
      model.data['url'] = urls[0]; // Only one video is allowed
    }
};

<FileUploadComponent
    fileTypes="video/*"
    label="Upload a Video"
    onUpdate={handleUpdate}
    multiple={false}
    maxFiles={1}
/>
```

This gives you a button that when clicked allows the user to a video from their camera roll. Check out the `VideoBlock` for a full example.

#### Uploading arbitrary files

For any other file types, you can use the Firebase uploader functions yourself. Read more in the [documentation here](https://github.com/capawesome-team/capacitor-firebase/tree/main/packages/storage). The strategy is 1) use the upload component to upload, and then 2) store the URL in your block.

#### Troubleshooting

If you get the error: `Error: firebase-tools no longer supports Java version before 11. Please upgrade to Java version 11 or above to continue using the emulators.`, you unfortunately need to download a new version of the JDK (Java Development Kit). Go to the [Oracle JDK Download page](https://www.oracle.com/java/technologies/downloads/#java11-mac), download the installer, and try starting the emulators again.


# Miniapp Icons

Lastly, don't forget to add your **icon** to `types.tsx`! This is the icon that will show when the user is browsing the miniapp list inside the Seam composer.

Save your miniapp icon as a PNG. Then add it to the `blockIcons` folder.


# FAQ

* My miniapp needs a dependency. How should I include it?
  * Add your new package using `yarn add`, and the main Seam application will bundle it when your block is accepted. Please don't add any extra UI libraries, as those can be very large, slowing down the entire Seam app.
* How do I let users upload Images/Videos/Files with my miniapp?
  * See the [Uploading Images, Videos, and Files](https://docs.getseam.xyz/miniapp-creation/uploading-images-videos-and-files) page under the Miniapp Creation section.
* My block needs an external API key. How do I make it work?
  * The unfortunate reality of our current walled garden internet is that much of it is gated behind API keys. Use `process.env` in your local development to insert an API key, and on miniapp submission we'll work with you to see if it makes sense for Seam to apply for a global API key for the service you want to make a miniapp for.
* I found a bug, and something is not working. How do I fix it?
  * [Create a new issue](https://github.com/seam-xyz/Miniapp-Builder/issues/new) in the Seam miniapp builder github repository, and we'll address it.


# Tips & Tricks

### Changing the iOS status bar color

If you'd like to change the top status bar color (where the iOS battery, clock, reception, etc. are displayed) for your fullscreen miniapp, you can do so via the following:

```
document.body.style.backgroundColor = "#YOUR_BACKGROUND_COLOR";
```

We currently implement this solution on several miniapps via the following example:

```
export const YourMiniappComposerComponent = ({ model, done }: 
  useEffect (() => {
    document.body.style.backgroundColor = "#YOUR_BACKGROUND_COLOR";

    return () => {
      document.body.style.backgroundColor = "#FFFFFF";
    }
  })
)
```

It's important to set the background color back to white on cleanup to ensure the status bar is restored to its original color.


# Creating a Pull Request

Submitting your code to Seam so that it can go live to everyone

Once you are happy with your miniapp, it's time to create a [Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) so your miniapp can go live on [seam.so](https://www.seam.so) and the Seam mobile app.

*A pull request is a proposal to merge a set of changes from one branch into another. In a pull request, collaborators can review and discuss the proposed set of changes before they integrate the changes into the main codebase. Pull requests display the differences, or diffs, between the content in the source branch and the content in the target branch. --* [Github documentation on Pull Requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests)

### Before you Submit

To help your pull request go as smoothly as possible, review the steps listed below that can slow down a review. Make sure you:

* Test your miniapp for crashes and bugs!
* Remove all `console.log` statements, commented code, and unused code. Remember, your miniapp will live open source as an example for new developers who come behind you!
* Make sure your miniapp works on phone sizes as well as desktop sizes. See [Supporting Mobile](/miniapp-creation/supporting-mobile) for more.
* Don't include any large new dependencies that would slow down the entire Seam loading experience.

We'll accept any miniapps that are genuine and fun and come from your own imagination. We strongly support all points of view being represented on Seam, as long as the miniapps are respectful to users with differing opinions and the quality of the miniapp is great. We won't merge any miniapps for any content or behavior that we believe is over the line.

### Making the Submission: Creating a Pull Request from a Fork

1. Navigate to the original repository where you created your fork.
2. Above the list of files, in the yellow banner, click **Compare & pull request** to create a pull request for the associated branch.

   ![Screenshot of the banner above the list of files.](https://docs.github.com/assets/cb-34097/images/help/pull_requests/pull-request-compare-pull-request.png)
3. On the page to create a new pull request, click **compare across forks**.

   ![Screenshot of the page to open a pull request. The "compare across forks" link is outlined in dark orange.](https://docs.github.com/assets/cb-41260/images/help/pull_requests/compare-across-forks-link.png)
4. In the "base branch" dropdown menu, select the branch of the upstream repository you'd like to merge changes into.

   ![Screenshot of the page to open a new pull request. The dropdown menus for choosing the base repository and branch are outlined in dark orange.](https://docs.github.com/assets/cb-96536/images/help/pull_requests/choose-base-fork-and-branch.png)
5. In the "head fork" dropdown menu, select your fork, then use the "compare branch" drop-down menu to select the branch you made your changes in. Likely, this will be the `main` branch of your fork.

   ![Screenshot of the page to open a new pull request. The dropdown menus for choosing the head repository and compare branch are outlined in dark orange.](https://docs.github.com/assets/cb-96331/images/help/pull_requests/choose-head-fork-compare-branch.png)
6. Type a title and description for your pull request, like: `[Miniapp Name Submission]: Description of miniapp`.
7. Make sure to select **Allow edits from maintainers** checkbox, as this will allow the Seam team to help clean up and get your miniapp ready to ship.
8. To create a pull request that is ready for review, click **Create Pull Request**. To create a draft pull request, use the drop-down and select **Create Draft Pull Request**, then click **Draft Pull Request**.

You can learn more about Pull Requests using the [documentation on Github](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request#changing-the-branch-range-and-destination-repository).

### Seam Team Review

Once your Pull Request is created, the Seam team will review the code and functionality of your miniapp. This [code review](https://about.gitlab.com/topics/version-control/what-is-code-review/) is an important part of making sure that all miniapps follow best practices for performance and code quality, so they are examples everyone else can follow in the future.

Keep an eye out for Requests for Changes, and you'll need to make sure to address the comments and update your code before the miniapp ships. Once the code is looking good, the Seam Team will accept the Pull Request and merge it into the main branch of the miniapp repository, and it will ship in the next release of the Seam app.


# Adding Teammates

If you have multiple teammates all working on the same miniapp, it's possible to add them as collaborators so that everyone gets Points when someone unlocks your miniapp. After all, on Seam we want to reward everyone who had a hand in building miniapps! It doesn't matter if you're a designer, engineer, or someone with good ideas, everyone should be recognized for the part they play in collaboration.

Inside of the `types.tsx` file where all of the miniapp types live, you'll see a definition for a `PointSplit`, which is used to determine what % of the total points each collaborator should receive.

```
export type PointSplit = {
  username: string;
  split: number; // 1.0 is 100%, 0.5 is 50%, etc. Make sure to add up to 1.0!
};
```

To add multiple collaborators, add another PointSplit object in the `createdBy` array, like so:

```
createdBy: [{username: "jamesburet", split: 0.5}, {username: "rocco", split: 0.5}],
```

All percentages must add up to 1.0. The split percentages are up to your team, and remember to fairly reward anyone who helped you build your miniapp. It goes without saying, but don't tamper with the percentages of other miniapps that aren't your own -- those pull requests will be rejected.


