📣 Use this repository as a starter project if you're a GitHub user interested in building your own custom Blocks!
This template is (built with Vite, React and Typescript) and we'll guide you through how to use it.
Here's a short tutorial video on how to get started creating your own block:
Blocks.-.tutorial.V2.mp4
Here's a short tutorial video on the Blocks API:
blocks-advanced-v1.mp4
This repo is already a template! To use it just click on the "Use this template" button on the top right to set it up for your use.
The button will take you to a screen to specify what you want to name your own repo.
yarn # install dependencies
yarn dev # start the dev server
A development server should now be running on localhost:4000.
When you visit localhost:4000 in your browser, you should see an interface which we'll call a "sandbox" that lets you test out local and production versions of your Block.
This starter project has one example folder block and one example file block.
blocksDemo.mov
You can play with two bits of the interface to view your Blocks:
- Input file or folder: An
input
that accepts the direct path to a file or a folder on github.com. Here are some valid file/folder paths.
- https://github.com/facebook/react/blob/main/README.md (file)
- https://github.com/facebook/react/blob/0.3-stable/README.md (file)
- https://github.com/facebook/react/blob/main/packages/react-dom/index.js (file)
- https://github.com/facebook/react/ (folder)
- https://github.com/facebook/react/tree/0.3-stable (folder)
- https://github.com/facebook/react/tree/main/packages (folder)
- List of custom blocks: A
select
that lists out the contents of theblocks
array that you have defined in/package.json
.
Once you've entered a valid path, choose from the different block types and the content should be rendered beneath. Be sure to check your console for errors if you think something has broken!
To create or customize your own custom blocks you need to do two things:
If you open up package.json
and locate the blocks
key, you'll notice an array of block objects with the definitions for each custom block. It looks lke this:
interface BlockDefinition {
type: "file" | "folder";
id: string;
title: string;
description: string;
entry: string;
extensions: string[]; // Soon to be deprecated in favor of the following "matches" key.
matches?: string[]; // An array of globs written in picomatch syntax. See https://github.com/micromatch/picomatch for examples.
example_path?: string;
}
You have to define these properties for your own custom Block.
From top to bottom:
type
determines whether this block applies to, well, files or folders.id
is the identifier string for this block: this needs to be unique within your project. GitHub Blocks uses this to determine which block to render.title
anddescription
are both presentational attributes that affect how the block will appear on the Blocks Marketplaceentry
is the most important attribute: its value should be a file path to your block's entry point (starting with/
- the root)extensions
is an array of file extensions (the text of a filename after the first.
), which lets GitHub Blocks know for which types of files this block should be listed.*
represents a wildcard value, meaning the block will always be listed.matches
is an array of globs (following https://github.com/micromatch/picomatch syntax), which lets GitHub Blocks know for which types of files this block should be listed.example_path
(optional) is the path to an example file that will be displayed in the block’s preview on the Blocks Marketplace.
Most of your code will go within: src/blocks/
.
This is where you'll find our two example blocks.
A Block is a React component that receives a special set of props and returns JSX. We've implemented two type of Blocks: File Blocks and Folder Blocks. Their API is largely the same, receiving the following props:
interface BlockProps {
block: {
id: string;
type: string;
title: string;
description: string;
entry: string;
extensions?: string[];
matches?: string[];
};
context: {
path: string;
file: string;
repo: string;
owner: string;
sha: string;
};
metadata: any;
// whether or not a user can edit the content
// for example, this will be false if they only have read access to the repo
isEditable: boolean;
// callback functions
onUpdateMetadata: (
newMetadata: any,
path: string,
block: Block,
currentMetadata: any
) => void;
onUpdateContent: (newContent: string) => void;
onRequestGitHubData: (
// this is any GET endpoint in the GitHub API:
// https://docs.github.com/en/rest/overview/endpoints-available-for-github-apps
// e.g. `/repos/{owner}/{repo}/contributors`
path: string,
params: Record<string, any>
) => Promise<any>;
onNavigateToPath: (path: string) => void;
// if a File Block
content: string;
// if a Folder Block
tree: {
path?: string;
mode?: string;
type?: string;
sha?: string;
size?: number;
url?: string;
}[];
}
For simple use cases, the content
(the content of the file) or tree
(a list of the contained files & folders) prop will be the most useful, with info about the file or folder the user is looking at on the GitHub Blocks UI. But if you need additional context (such as the path to the file or the owner/repo in which the file lives), you can access it via the handy context
prop.
metadata
is a free-form prop that can be used to store arbitrary data about the file. It's up to you to decide what you want to store in this object: anywhere from definitions of data visualizations in a charts Block to annotations for a code Block. This is unique per file/folder per Block and stored within a .github/blocks/file/
folder within the viewed repo. To update the metadata, you can call the onUpdateMetadata
prop with the updated data, which creates a new commit on the repo.
A few caveats and callouts:
- Blocks have access to GitHub Primer React components
- You can use both third-party and relative imports in your Block code! Simply put, feel free to install any dependencies from NPM, or import a local JS/CSS file and it should be included in the final bundle.
- Your Block entry file must have the Block component as its default export. If it does not, bad things will happen.
- To make authenticated requests to the GitHub API, create a personal access token (with
repo
scope) and pass it when you start the dev server:
VITE_GITHUB_PAT=${your personal access token} yarn dev
or put it in your .env
file in the project root directory.
Example blocks that we've built to showcase the API.
To reduce the cognitive load associated with writing file and folder Block components, we've assembled a helper library called @githunext/utils
that exposes interface definitions and a few helper functions. This list will undoubtedly change over time, so be sure to check out the repository page for more detail.
We've built a Blocks Marketplace where anyone can find and use your Blocks!
In order to include your custom blocks within that marketplace you have to do a few things:
You need to tag this repository with the topic github-blocks
so we can find your repository.
To build a production version of your app, we've included a build system within this template that handles everything for you (a combination of GitHub actions within the .github/workflows
folder and a build.ts
script).
Don't worry! We deal with a lot of this complexity, all you have to do is create a new tag to kick-start the build process.
git tag 0.9.0 # Create a new tag with your own version number
git push --tags # Push the tag to GitHub
Pushing a new tag should kick-start a GitHub action that builds your relase. Wait for that to finish and find a release with your same tag number.
We can only detect Blocks in public repos!
Every hour we search GitHub for new custom Blocks. Wait about an hour before you can see your blocks on the Blocks Marketplace.
- When developing folder blocks you might hit a "Something went wrong" message if you reach the rate limit of the GitHub API. This is because we're hitting the API unauthenticated.