NAV Navbar

Openframe Documentation


What is Openframe?

Openframe is an open source platform for artists, curators and art enthusiasts to share, discover and display digital art.


Openframe is free. Anyone can set up a frame using an HDMI display and a Raspberry Pi.

Openframe is a collaborative, on-going project. The platform consists of an API, a web app, and software for the RPi that currently supports images, video, web-based artwork, and shaders (more on this in the Platform section). Our goal is to create a system that is accessible and extensible, allowing artists to easily add support for new digital formats.

Go to for more info.

Bugs, contributions and feedback

Feedback and contributions are welcome!

Please post questions to the Openframe Forum so that others may benefit! Specific software bugs can be submitted as Github issues in the appropriate repo (E.g. Raspberry Pi, Web App, etc.).

Please help improving these docs!

Documentation Topics

Getting Started

This guide tells you everything you need to know to set up and use a frame quickly. We're going to add more in-depth information on specific topics elsewhere in this documentation over time.

1. Create a user account

Go to and create a new account.

2. Setup a frame

Openframe is designed for the Raspberry Pi although it might run on other computers.

Hardware requirements

If you're looking for a Raspberry Pi starter pack, this would work well: Starter pack

Head over to the Hardware section for more info.

2.0 Prepare SD card

Follow the Raspberry Pi installation guide and copy the latest version of the Raspian operating system on to the SD card. Alternatively, use a pre-flashed SD-card with NOOBS.

Or you can download an SD image with Openframe preinstalled. If you choose this option, set up the hardware (as described below) boot the Pi, log in and jump to step 2.3 Start the frame.

2.1 Configure the Pi

  1. Set up hardware: Insert the SD card, WiFi dongle, and connect the monitor, keyboard and mouse.
  2. Plug in the Pi Your Pi should boot into Desktop.
  3. Connect to the internet if you are not connected via ethernet, click the network icon in the upper right-hand corner, turn on WiFi and select your WiFi network.
  4. Configure: Open the Menu (top left corner) > Preferences > Raspberry Pi Configuration.
  1. Finish Click OK to close the configuration tool and reboot.
  2. When the Pi reboots, login with the root user pi and password raspberry, unless you changed it.

2.2 Install Openframe

$ bash -c "$(curl"

In the command line on the Raspberry Pi, execute the install shell script. The installation takes around 20 minutes (could be longer on a slow connection). Follow the instructions at the end of the installation.

You need to restart the Pi before the next step.

2.3 Start the frame

After installation, just type openframe at the command line.

If it's the first time you start the frame, it will ask you for your Openframe username and password, a name for this frame, and if you want to boot into openframe automatically when the Pi starts.

$ openframe
? Enter your Openframe username: lewisc
? Enter your Openframe password: ****
? Enter a name for this Frame: Living Room Frame
? Do you want to boot openframe on startup? (Y/n): No

[o]   Connected! You can now push artwork to this frame.

This frame should now appear as Living Room Frame when you log in to Openframe at

You're now ready to start displaying artwork!

3. Displaying artwork

  1. Log in to the Web-App using your account.
  2. You can find artwork on the public stream or your personal artwork collection.
  3. Pick something you like and click the arrow button to push the artwork to your frame.

    S 47c66c780b27d1d2264ac535f55118b0b8c6f1be72f9b3ad04cfc94291323268 1573847491114 image The artwork should show up on the frame a few seconds later.

These are some artwork examples of different formats you can try out straight away:

🙌 That's all. Enjoy your Openframe!

What's next? Now you are probably keen to add your own artwork. More on this here.


Public Artwork stream

The Stream contains all artwork that users have made public. You can like artwork from the Stream to save it. Click the like button again to remove it.

Your Artwork collection

Your artwork collection contains artwork that you've added, or you've liked from the Stream. You can find it clicking on the YOU button next to the BROWSE in the stream.

It's located at this URL:{username}

Adding artwork

You can add artwork through the Openframe web-app.

  1. Create a new account, if you haven't done already.
  2. Then click on You to navigate to your personal artwork stream (which includes all artwork you have uploaded) and click Add artwork to add a new piece. S 47c66c780b27d1d2264ac535f55118b0b8c6f1be72f9b3ad04cfc94291323268 1573847631491 image
  3. Your artwork must have a title.
  4. Choose the right artwork format (e.g. openframe-image). More on this further below in the Artwork formats section.
  5. Enter the URL to your artwork into the field URL where the artwork is hosted. You have to host the artwork yourself.
  6. Optionally enter a URL to a preview image of the sketch which is useful to quickly identify the artwork in the web-app. This is even more important if you like to publish your beautiful artwork.

Read more on how to push uploaded artwork to your frame.

Per Artwork settings

Some extensions make use of settings attached to each artwork. While there is no UI for this in the Web app yet, you can still use the API / JavaScript client to apply settings to an artwork. There is a guide on adding settings for the slideshow extension using the API available in the forum.

Hosting artwork

If you don't have a webspace/server?

You could, as one out of many options, try Dropbox. Once it's uploaded to Dropbox, use the Copy Dropbox Link. You will end up with a URL like this Change the 0 at the end to 1 like this, and you will be able to use the URL for Openframe.


The artwork will be available as long as the content in the URL is available.

Display Controller

The display or frame controller is the software that runs on the frame itself (i.e. the RPi), acting as a process manager for starting, stopping, and transitioning between artworks. You can remote control it via the Web-App. Both communicate via an API Server (more on this in the Platform section).

The following are topics interesting to everyone running a frame.


Openframe provides a baseline functionality that can be augmented with extensions. An extension may be created to support a new artwork format, to add interactivity to the frame, etc.

Installing / removing an extension

E.G, to add the openFrameworks extension:

$ openframe -i openframe-of

To remove the openFrameworks extension:

$ openframe -u openframe-of

At present, extensions must be installed and removed on the Raspberry Pi directly, via the command line.

Artwork formats and extensions

By default, Openframe supports four types of artwork formats:

At the moment each artwork format is coupled to a specific extension (this might change).

The following list should help you finding the right extension for the media type you would like to use.









There is a slideshow extensions available which loops through your artwork collection. In theory it can loop through other collections of artworks too, but at this stage the only collection available is your personal one.

Install it like any other extension on the display controller:

$ openframe -i openframe-slideshow

The default interval between two artworks is 1 minute. You can adjust this either through the frame settings file applying to all artworks. Or by adding per-artwork settings using the API. There is no UI for this yet. There is a guide on doing this using the API available in the forum.


At the terminal, open crontab config:

$ crontab -e

and add the following cron rules:

00 23 * * * vcgencmd display_power 0
30 7 * * * vcgencmd display_power 1

If you want your frame to go to sleep at certain hours, edit crontab.

The example to the right will turn OFF the display of the frame at 23:00, and turn it ON at 7:30 in the morning. Change the values for different times. Learn more about crontab to setup different timer for different days of the week.

Change the rotation of the display

At the terminal, edit /boot/config.txt:

$ sudo nano /boot/config.txt

then add the desired display_rotate setting:


If you want to change the orientation from what was set during the installation, edit the Raspberry Pi configuration file (/boot/config.txt).

0 is the display default (landscape). 1 will rotate the display by 90° counterclockwise. Use 2 for 180°, or 3 for 270°.

Adding additional curators

If you're a frame owner (i.e. you've created it using your username) you can add other users as curators. Curators will see another frame in their list of frames, and will be able to push artwork to it. They cannot edit the frame settings or delete it.

Curators are added via the web app, within the frame's settings panel.

Resetting a frame

$ openframe -r

A frame can be reset to its default state — that is, a blank frame instance not yet attached to any particular account — by passing the -r flag at startup. This will erase the user and frame data stored on the RPi, and will prompt you once again for your username, password, and a name for the frame. Once a frame is reset, it's previous state cannot be restored (though this is generally not an issue... you'll just need to start pushing artwork to the new frame).

Resetting a frame will not remove it from your frame list in the web app; you will need to remove the instance of the old frame manually via the UI, under the frame's settings.

Frame settings

The frame's settings file is located on your device at: ~/.openframe/.ofrc


Re-run the install script, then source your .bashrc file:

$ bash -c "$(curl"
$ source ~/.bashrc

Then run openframe:

$ openframe

If you've already installed Openframe via the install script above, you can simply re-run the install script to update.

After upgrading, you'll need to reload your shell in order to pull in any enviroment changes.



Although technically it can run on any computer that runs Node.js, Openframe is designed for the Raspberry Pi.

All versions of Raspberry Pi should work:

If you're looking for a Raspberry Pi starter pack, this would work well: Starter pack

Other computers and operating systems

There are some attempts to run Openframe on a proprietary frames like Electric Objects or Memento Smart Frame. Refer to the Hardware section of the forum for more info, share your own progress or ask questions on that matter.

The idea to build a browser based version of Openframe came up. It would run on any device with a browser – like PCs, tablets or smartphones.


Most monitors should work. Just make sure it connects to the HDMI port of the Pi, not GPIO or HAT. If your monitor doesn't have an HDMI port you can also use an adaptor to DVI / Display Port / VGA / etc..

If you are choosing a monitor these are things you might want to consider:

A couple of specific models have been mentioned in the forum.

Monitor Frames

You might consider framing your monitor to improve the design or make it look more like a classic picture frame.

Framed monitor showing artwork from Patricio Gonzalez Vivo and Anders Hoff There is a building guide based on a picture frame.

Other users have built one with extra physical buttons or had one built in a professional framing shop.

Please share your framing projects in the Woodwork category of the forum.

Strip down

Another option is to strip down the plastic case and reveal the bare-bone display. This has been done for the Alt-AI / Openframe exhibition, for example.

GPIO for electronic components

Using the GPIO of the Raspberry Pi you can extend your frame with electronic components like sensors, buttons, motors and more. The sky is your limit. This GPIO example Openframe extension might help you getting started.


The platform consists of an API, a web app, and software for the RPi that currently supports images, video, web-based artwork, and shaders. Our goal is to create a system that is accessible and extensible, allowing artists to easily add support for new digital formats.

The block diagram below represents a proposed architecture for the Openframe platform. It will continue to evolve as development on the project progresses.

block diagram of proposed architecture for the Openframe platform

The Openframe controller is the software that runs on the frame itself (i.e. the RPi), acting as a process manager for starting, stopping, and transitioning between artworks. It communicates with an Openframe API Server server via a REST API, and connects to a global event system allowing for realtime updates. The idea is to work towards a system which supports the basic goals of Openframe, guided by a handful of pilot use cases.

Development Environment

If you're planning to hack on Openframe, you'll want to set up a dev environment which makes it easy to update code and see the results right away. Openframe uses familiar tools like GitHub, Node, and NPM, and we'll use some of the standard development practices of these tools to get setup.

This guide is for people developing on the Raspberry Pi. Here we'll describe setting up an environment suitable for working on the Openframe frame controller and modifying or developing new extensions. This setup will still use the public Openframe server, so you can use your account at to add and push artwork to your modified frame.


  1. Create an Openframe-ready SD Card following the Getting started guide. Enable SSH and, optionally, setup Samba.
  2. Fork the Openframe repo, and any extension repos you plan to work on / mess around with.
  3. On the Pi, clone each forked repo, and npm install their dependencies.
  4. In each of the extension repos, use npm link to create a global symbolic link for this npm package
  5. In the Openframe repo, use npm link [extension-a-package-name] [extension-b-package-name], passing each linked extension package name.
  6. In the Openframe repo, run npm start. Now Openframe is running from the source code, using the source code of the extensions.
  7. Get to work. You can SSH into the Pi and edit files directly there, or use Samba to mount the files on your computer and work on them with your favorite editor.

The Long Version

Create an Openframe SD Card

In order to make sure the Pi has all of the necessary dependencies installed, the simplest thing to do is to set up an SD Card following the Getting started guide. Make sure you've got an Openframe account and that you can log in and push artwork to your frame. You probably don't want to enable 'autoboot to Openframe', since you'll be launching Openframe yourself.

It's helpful to enable SSH on the Pi so that you can edit files remotely. If you're comfortable setting up and using vim (or whatever editor) on the Pi and doing your development work there, that's fine. If you'd prefer to use your usual editor, you can use Samba to mount the Pi's file system on your computer and edit the files directly.

There's a good tutorial on setting up Samba on the openFrameworks site.

Fork the Openframe Repo

You'll want to fork the Openframe repo to your own GitHub account. This way you'll be able to keep track of your work and contribute bug fixes or enhancements 😄.

More info on forks and the GitHub collaborative workflow is available on the GitHub site.

Clone and Install the Repos to the Pi

On the Pi, clone your forks of the repos you want to work on, and install their deps:

$ git clone
$ git clone
$ git clone
$ cd ~/Openframe && npm install
$ cd ~/Openframe-Image && npm install
$ cd ~/Openframe-MyNewExtension && npm install

As a quick test, after all deps have installed, run Openframe from source:

$ cd ~/Openframe
$ npm start

Log into the Pi terminal, via SSH or directly, and git clone your repos. You'll clone your forked Openframe repo, and whatever extension repos you're working on. These might be forks of existing extensions, or a repo for a new extension.

Once you've got the source code for each project on the Pi, npm install in each repo directory to install all of the dependencies. Some extensions take quite a while to install!

As a quick test, you can run the Openframe from the source code and make sure it works. Just go to the Openframe project dir and type npm start. If you've already logged in when you initially installed openframe, the frame should start using the same credentials. It may ask if you want to autoboot, which you should decline.

E.g., assuming you're working on the Openframe-Image extension, and you've cloned all of the repos into the pi user's home folder:

$ cd ~/Openframe-Image
$ npm link
$ cd ~/Openframe-MyNewExtension
$ npm link
$ cd ~/Openframe
$ npm link openframe-image openframe-mynewextension
$ openframe -i openframe-image 
$ openframe -i openframe-mynewextension

Extensions are just Node packages, following standard NPM package practices. They are specified by their NPM package name, as defined in the package.json file. The package name must be all lowercase, and as a convention should follow the pattern 'openframe-[extension-name]'. For example, the Openframe-glslViewer extension has a package name of 'openframe-glslviewer'.

If you are indeed working on extensions, you need to make sure that the local version of Openframe you're running is pulling in the local versions of the extensions. As this is a common need when developing inter-dependent projects, NPM provides a handy way of managing this. You'll use NPM's link command to create a global symlink for the dependencies, then use it again to tell the dependent project to use the symlinked version.

In each of the extension project directories, run npm link. This will create the global symlink.

Then in the Openframe project directory, run npm link [extension-name] [another-extension-name], passing each package name (from package.json) for the extension repos you'll be working on.

Get to Work

That's it! Now the Openframe project will point to the linked extensions, so changes to the extension's files will take effect immediately within the Openframe project. Great!

Whenever you make changes to files, you'll need to restart Openframe on the Pi by running npm start in the Openframe project directory.


Openframe uses (and encourages you to use!) the debug package. You can see the debug output by specifying the DEBUG env var when starting Openframe. E.g. to see all debug output:

$ DEBUG=* npm start

Creating an extension

Extensions are node modules which export an instance of the Extension class. The README of the Extension repo gives a bit of information about how Extensions work, and how to create them.

If you're interested, take a look at the source for default extensions (openframe-image, openframe-website, openframe-glslviewer, and openframe-video) to get a sense of how they work in practice.

Keep in mind that Openframe is still in an early alpha state, and the way extensions are created and loaded will continue to evolve and improve!

JavaScript Client

Welcome to the Openframe JavaScript Client! You can use this lib to access Openframe REST API endpoints, which can be used to fetch and update artworks, frames, and profile information for authenticated users.

At present we only have this JavaScript client library, but you're welcome to use any language to interact with the API directly.

All of the methods of the JS client return Promises which are resolved with the parsed response body or are rejected with an error message.

Initializing the JS Client

import OF from 'openframe-jsclient';

// use default openframe server,
const OF = new OF();

// to use a different server, pass option to constructor
const OF = new OF({
  api_base: 'http://localhost:8888/api'

Import the Javascript client class and create a new instance, optionally passing configuration options.

Configuration Options

Option Default Description
api_base The base URL where the API is located

Authentication / Authorization

Although some Openframe data is public and can be retrieved via unauthenticated access, the API requires an authenticated user's access token to be supplied with requests in order to manipulate a user's data. An access token can be obtained by hitting the login endpoint and providing a valid username (or email) and password.

Once obtained, the access token may be included in requests as a query param:


or in an Authentication header:

Authentication: wtu2jsJYZTO8ZqjGokR1ejznxCw4Qd0hACFo50GXyx3eGcVNNroccDWHZHmHVXKn

As noted below, after successful login, the JS client manages storage and inclusion of the header (in browsers with localStorage available).

Log in

This assumes you've got an instantiated OF client.

OF.users.login({'username': 'test', 'password': 'test'})
  .then(token => {

// or

OF.users.login({'email': '', 'password': 'test'})
  .then(token => {

If valid credentials are passed, the resolved token object, looks like this, where "id" is the actual access token value:

  "id": "wtu2jsJYZTO8ZqjGokR1ejznxCw4Qd0hACFo50GXyx3eGcVNNroccDWHZHmHVXKn",
  "ttl": 1209600,
  "created": "2016-12-05T03:45:55.936Z",
  "userId": "56c47fba45e503657a51bebc"

On success, the login endpoint responds with an access token that is valid for two weeks or until explicity destroyed via logout. The token is presented as the id value of response body, and must be supplied with subsequent requests on behalf of the logged in user.

If localStorage is present (i.e. the browser), the JS client automatically saves the access token to localStorage and includes it in subsequent responses.

Log out

Calling logout will destroy the access token on the server and, if present, clear it from localStorage. Successful logout will result in a 204 no content response and resolve the Promise. If there is any problem destroying the access token on the server, the promise will be rejected with an error.

  .then(() => {
    // success, no response body
  .catch(error => {


Fetch a list of Users

  .then(users => {
    // do something with users

The above users array is structured like this:

    "full_name": "Sol Lewitt",
    "username": "slewitt",
    "created": "2016-02-17T14:12:10.463Z",
    "id": "56c47fba45e503657a51bebd",
    "modified": "2016-02-17T14:12:10.463Z"
    "full_name": "Peter Pan",
    "username": "ppan",
    "created": "2016-02-17T14:12:10.560Z",
    "id": "56c47fba45e503657a51bebc",
    "modified": "2016-02-17T14:12:10.560Z"
    "full_name": "Missy Elliot",
    "username": "melliot",
    "created": "2016-02-17T14:12:10.666Z",
    "id": "56c47fba45e503657a51bebe",
    "modified": "2016-02-17T14:12:10.666Z"

This endpoint retrieves a list of users. At present, limited information is public for all Openframe users.


Arg Default Description
filter {} A filter config object

Fetch a User by ID

  .then(user => {
    // user is currently authenticated user

OF.users.fetchById("56c47fba45e503657a51bebd") .then(user => { // do something with user });

The above user object is structured like this:

  "full_name": "Sol Lewitt",
  "username": "slewitt",
  "created": "2016-02-17T14:12:10.463Z",
  "id": "56c47fba45e503657a51bebd",
  "modified": "2016-02-17T14:12:10.463Z"

This method retrieves a specific user by ID.

If no ID is passed, it defaults to 'current', which returns the currently authenticated user.


Arg Default Description
ID 'current' A user ID

Fetch a User by username

  .then(user => {
    /* user looks like
      "full_name": "Sol Lewitt",
      "username": "slewitt",
      "created": "2016-02-17T14:12:10.463Z",
      "id": "56c47fba45e503657a51bebd",
      "modified": "2016-02-17T14:12:10.463Z"

This method retrieves a specific user by Username.


Arg Default Description
username none A username


Fetch all current User's frames


Fetch a list of Artworks

  .then(artworks => {
    // do something with artworks

The above artworks array is structured like this:

    "title": "unknown-1471551583623.frag",
    "is_public": true,
    "url": "",
    "thumb_url": "",
    "author_name": "unknown",
    "required_extensions": {},
    "format": "openframe-glslviewer",
    "id": "57b61d12c0006da8310e9143",
    "ownerId": "57b61cf8c0006da8310e9141",
    "created": "2016-08-18T20:39:46.662Z",
    "modified": "2016-08-18T20:39:46.662Z"
    "title": "Nutrition Facts",
    "is_public": true,
    "url": "",
    "thumb_url": "",
    "author_name": "Leah Valle",
    "required_extensions": {},
    "format": "openframe-website",
    "id": "57a85d5bc0006da8310e906b",
    "ownerId": "57a85cf2c0006da8310e9069",
    "created": "2016-08-08T10:22:19.405Z",
    "modified": "2016-08-08T10:22:19.405Z"

This endpoint retrieves a list of Artworks. If called by an unauthenticated user, the list will include only public Artworks. If called by an authenticated user, private Artworks which the user has added will also be present in the response.

Fetch an Artwork by ID

Filtering queries

// an example filter for an OF.users.fetch(filter)
let filter = {
  fields: ['username', 'id', 'website'],    // fields to include
  include: 'created_artwork',               // include a relation in the result
  limit: 10,                                // limit the number of results
  skip: 10,                                 // skip a number of results
  order: 'username ASC',                    // result order direction, ASC or DESC
  where: {                                  // filter by field data
    username: {
      like: 'abc'                           // lots of operator options

All of the fetch methods can take an optional filter config object, which allows filtering of the query results. Whenever available, it takes the form shown on the right.