> ## Documentation Index
> Fetch the complete documentation index at: https://openops-ecb4f397-ops-3865.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating a Minimal Block

> How to quickly create a new minimal OpenOps block

Let's see what it takes to create a new OpenOps block, add an action to it, and view it in the OpenOps UI.

Before you begin, make sure you've [set up your development environment](/contributing/development-environment/).

## Scaffold a new block

To get started, generate a new block by running the following command:

```bash theme={null}
npm run cli blocks create
```

You will be asked four questions to define your new block:

1. `Block name`: enter `my-unique-block`: a name that uniquely identifies your block in the OpenOps repository.
2. `Package name`: optionally, enter a name for the npm package associated with your block. If left blank, the default name will be used: `@openops/block-<block-name>`.
3. `Authentication type`: select `None` for now, as this minimal block will work with a publicly available API. Other options are `Secret (API key)`, `Custom (custom properties)`, and `OAuth2 (OAuth2 flow)`.
4. `Create opinionated folder structure with stubs for actions, tests, and common service layer`: select `Yes - Create full folder structure with stubs`.

OpenOps will generate a full project template inside `packages/blocks/<your-block-name>/`. The scaffolded files are as follows:

* Source and tests:
  * `src/index.ts`: the main entry point of your block, where you'll define its metadata (name, display name, description) and register actions.
  * `test/index.test.ts`: a starter Jest test file where you can add unit tests for your block.
* Project metadata:
  * `package.json`: declares the block as an npm package.
  * `project.json`: configuration for the Nx build system that OpenOps uses.
  * `README.md`: a starter readme file describing what the block does and how to use it.
* Tooling and quality:
  * `.eslintrc.json`: ESLint rules for enforcing code style and catching errors.
  * `jest.config.ts`: configuration for Jest, the test runner used by OpenOps.
* TypeScript setup:
  * `tsconfig.json`: base TypeScript configuration for the block.
  * `tsconfig.lib.json`: TypeScript settings for the block's source code.
  * `tsconfig.spec.json`: TypeScript settings for the block's tests.
  * `tsconfig.base.json` (in the repo root): serves as a central list of all TypeScript settings, and the CLI updates it to include your new block.

The `src/index.ts` file should contain the following code:

```ts theme={null}
import { BlockAuth, createBlock } from '@openops/blocks-framework';

export const myUniqueBlock = createBlock({
  displayName: 'My-unique-block',
  auth: BlockAuth.None(),
  minimumSupportedRelease: '0.20.0',
  logoUrl: 'https://static.openops.com/blocks/my-unique-block.png',
  authors: [],
  actions: [],
  triggers: [],
});
```

## Create an action

Now let's create the first action, which will make a simple GET request to [httpbin.org](https://httpbin.org/).

```bash theme={null}
npm run cli actions create
```

You will be asked four questions to define your new action:

1. `Enter the block folder name`: enter the folder of the block you've just created: `my-unique-block`.
2. `Enter the action display name`: enter the name of the action that users see in the OpenOps UI. This is also used as the base for the name of the file for the action. For this exercise, enter `My first action`.
3. `Enter the action description`: this should be a brief but informative text in the UI, explaining the action's function and purpose. For this exercise, it's OK to enter `What this action does`.
4. `Does this action modify data or state (e.g., create, update, delete)?`: enter `n` for "No".

If the action display name is `My first action`, the CLI will create a new file named `my-first-action.ts` in the `packages/blocks/<your-block-name>/src/lib/actions` directory. The file should look like this:

```typescript theme={null}
import { createAction, Property } from '@openops/blocks-framework';

export const myFirstAction = createAction({
  isWriteAction: false,
  name: 'myFirstAction',
  displayName: 'My first action',
  description: 'What this action does',
  props: {},
  async run() {
    // Action logic here
  },
});
```

Inside this file, paste the following code instead:

```typescript theme={null}
import { createAction, Property } from '@openops/blocks-framework';
import { httpClient, HttpMethod } from '@openops/blocks-common';

export const myFirstAction = createAction({
  isWriteAction: false,
  name: 'myFirstAction',
  displayName: 'My first action',
  description: 'What this action does',
  props: {},
  async run(context) {
    const res = await httpClient.sendRequest<string[]>({
      method: HttpMethod.GET,
      url: 'https://httpbin.org/get',
    });
    return res.body;
  },
});
```

Here's what happens in the code above:

* The `createAction` function takes an object with several properties, including the `name`, `displayName`, `description`, `props`, and `run` function of the action.
* The `props` property is an object that defines the properties the action needs the user to configure. In this case, the action doesn't require any properties.
* The `run()` function is called when the action is executed. It takes a single argument, `context`, which contains the values of the action's properties. In this case, the action doesn't have any properties, so the `context` parameter remains unused.
* The `run()` function uses the `httpClient` helper provided by OpenOps to make a GET request to [httpbin.org](https://httpbin.org/).
* The `run()` function returns the body of the response to the GET request, and this is what the action provides as its final output.

## Reference the action in the block definition

To make the action discoverable by OpenOps, import it into the block definition file at `src/index.ts` and add it to the `actions` array in the definition:

```typescript theme={null}
import { BlockAuth, createBlock } from '@openops/blocks-framework';
import { myFirstAction } from './lib/actions/my-first-action';

export const myUniqueBlock = createBlock({
  displayName: 'My-unique-block',
  auth: BlockAuth.None(),
  minimumSupportedRelease: '0.20.0',
  logoUrl: 'https://static.openops.com/blocks/my-unique-block.png',
  authors: [],
  actions: [myFirstAction],
  triggers: [],
});
```

## See your action in the OpenOps UI

Once you reference an action from your block definition and the running `npm start` process rebuilds OpenOps, you should see your action in the OpenOps UI for the first time:

<img src="https://mintcdn.com/openops-ecb4f397-ops-3865/VKS8haM5H7drDOHi/images/contributing/action-debuts-in-openops-ui.png?fit=max&auto=format&n=VKS8haM5H7drDOHi&q=85&s=69480f1a9e2005ce76c275e6048b141e" alt="Your action in OpenOps UI" width="1469" height="1383" data-path="images/contributing/action-debuts-in-openops-ui.png" />

If you use the action, you can see that it doesn't contain configurable properties apart from the two switches that OpenOps makes available to all actions. This is expected, as you haven't defined any properties in your action:

<img src="https://mintcdn.com/openops-ecb4f397-ops-3865/VKS8haM5H7drDOHi/images/contributing/action-properties-none.png?fit=max&auto=format&n=VKS8haM5H7drDOHi&q=85&s=3299af52b4d7078a05679c6c87ceb993" alt="Action properties" width="1611" height="1209" data-path="images/contributing/action-properties-none.png" />

If you go to the **Test** tab and click **Test Step**, you can see that it successfully returns a response from an API call to [httpbin.org](https://httpbin.org/):

<img src="https://mintcdn.com/openops-ecb4f397-ops-3865/VKS8haM5H7drDOHi/images/contributing/action-testing.png?fit=max&auto=format&n=VKS8haM5H7drDOHi&q=85&s=4222672721b4248d8b79376ca74e2bd1" alt="Testing the action" width="1610" height="1231" data-path="images/contributing/action-testing.png" />

Congratulations! You've just created your first OpenOps block and action. The block doesn't do a lot, and if you're wondering how to develop a block that does something useful, such as implementing a working integration with a third-party API, check out the [Contributing an Integration](/contributing/contributing-an-integration/) guide.
