The GraphQL surface of the CodePen API v2: one endpoint, and you ask for exactly the fields you want. It offers the same operations as the REST API v2, and both run the same logic, so a change made through one is visible through the other. Keys, scope, limits and error codes are shared and described on the API v2 page.
Endpoint
POST https://api.codepen.io/v2/graphql
Authorization: Bearer cp_user_your_key_here
Content-Type: application/json
{ "query": "...", "variables": { ... } }Code language: HTTP (http)
A request carries one operation. A JSON array of bodies (batching) is refused, so one request is one tick of the rate limit. A request that uploads a file is a multipart form instead of JSON; see Uploads below.
Playground
We have a GraphiQL Playground here:
https://api.codepen.io/v2/graphiql
Set the Authorization header in its settings, and you can explore the schema and run operations from the browser.
Query Complexity
Queries are limited by complexity, the number of fields they can touch. Each list costs its page limit (default 20, at most 100) times the fields selected under it, and a query is refused past 10,000. A page of 100 Pens with their titles costs 200. 100 Pens each with 100 versions and their files does not fit, so page the inner lists.
Conventions
- Every mutation takes one
inputobject and returns a payload object:PenPayload { pen build },PenFilesWritePayload { pen version build },CollectionPayload { collection },AssetPayload { asset }. - Every write to a Pen’s files makes a new version and builds it, and the compiler may change your files along the way. Wait for the build and read the files again before writing again (see Builds).
- Every list is paginated with the same
PaginationInputand returnsnodes,pageInfoandtotalCount, the usual connection shape without edges. Top-level lists take it asinput: { pagination: {...} }beside their filters;Collection.pensandPen.versionstake it asinputdirectly. - Enum values are PascalCase:
Private,Editor,CreatedAt,Classic. accessisPublic,Private,ProtectedorCollaboratorsfor Pens andPublicorPrivatefor Collections.editorsays which editor a Pen belongs to:V2orClassic. Classic Pens are read-only and read asfiles; versions, builds and deployments exist for 2.0 Pens only.- Timestamps are RFC 3339 (
Time). IDs are the ones in CodePen URLs. - There is no query for the key owner: the key says who that is, and every list (
pens,collections,assets) is theirs.
Operations
| Query | Returns |
|---|---|
pen(id) | One of your Pens, by cpid or hashid. |
pens(input) | Your Pens, filtered (see Filters). |
penVersion(id) | A saved version of one of your Pens. |
build(id) | A version’s build, by the version’s ID. This is the query to poll while a build is running (see Builds). |
collection(id) | One of your Collections. |
collections(input) | Your Collections; access filter. |
asset(id) | An Asset, a file in your Asset Hosting. |
assets(input) | Your Assets. |
| Mutation | Effect |
|---|---|
penCreate | A new 2.0 Pen; starter files unless files are given; access with password. Builds it. |
penFork | Copies one of your 2.0 Pens into a new Pen; access with password. Builds it. |
penUpdate | title, description, tags (replaced), template, access with password. |
penDelete | Moves the Pen to the trash. It’s listed under Deleted Items on the site and can be restored for 3 days. |
penFilesWrite | Applies file changes as one new saved version and builds it (see Builds). |
penFileWrite, penFileDelete | One-file shorthands for penFilesWrite. |
penVersionRestore | Copies an older version to the head. Not built again. |
penDeploy, penUndeploy | Deploys a version (default: the saved head); takes the deployment down. |
penSettingsUpdate | autoDeploy, autoRun, autoSave, indentWith, tabSize. |
collaboratorInvite | Invites a User by username or email as an Editor or Viewer. |
collaboratorUpdateRole, collaboratorRemove | Changes or removes a collaborator by username. |
collectionCreate, collectionUpdate | title, description, access. |
collectionDelete | Moves the Collection to the trash; its Pens are untouched. Like a Pen, it’s listed under Deleted Items on the site and can be restored for 3 days. |
collectionAddPen, collectionRemovePen | Membership. |
assetCreate | Adds a file to your Asset Hosting: filename, then content (text) or upload. |
assetUpdate | Renames (filename), replaces the file (content or upload), or both. |
assetDelete | Deletes an Asset for good. |
Types
Comments give the value set or a note where it is not obvious.
type Pen {
id: ID! # cpid for 2.0 Pens, hashid for Classic Pens
title: String!
description: String!
tags: [String!]!
access: AccessLevel! # Public | Private | Protected | Collaborators
password: String # write-only: always null
template: Boolean!
popularity: Int! # the site's score; lists can sort by it
editor: PenEditor! # Classic | V2
createdAt: Time!
updatedAt: Time!
urls: PenUrls!
screenshotURLs: PenScreenshotURLs # null for a V2 Pen with no saved version
files: [PenFile!]! # the saved head's files; built for Classic Pens
savedVersion: PenVersion # null for Classic Pens
version(id: ID!): PenVersion
versions(input: PaginationInput): PenVersionPage! # empty for Classic Pens
deployment: PenDeployment # null while not deployed
collaborators: [Collaborator!]!
collaboratorInvites: [CollaboratorInvite!]!
settings: PenSettings!
}
type PenUrls {
editor: String! # the Pen in the editor
preview: String! # the rendered saved head on its own; 404s for Protected and Collaborators Pens
}
# Screenshots of the saved head: JPEGs, 16:9, rendered on first request (so
# the first fetch can take a few seconds). Like urls.preview, Protected and
# Collaborators Pens need a browser session and answer a not-found image.
type PenScreenshotURLs {
small: String! # 320 pixels wide
medium: String! # 512
large: String! # 800
xlarge: String! # 1280
}
type PenFile {
id: ID! # stable across versions, renames and edits
path: String! # "/src/index.html"
name: String!
contentHash: String!
isBinary: Boolean!
content: String # null for binary files
url: String # the file served raw; null for Classic Pens
}
type PenVersion {
id: ID!
penID: ID!
hash: String!
createdAt: Time!
build: Build! # status, diagnostics, patches, output; see Builds
deployed: Boolean!
files: [PenFile!]! # the source files
previewUrl: String # the rendered version on its own
}
type Build {
id: ID! # the ID of the version it belongs to
status: BuildStatus! # Queued | Running | Success | Failed | Canceled
startedAt: Time
endedAt: Time
rounds: Int! # compiler builds it took; 1 when no changes were needed
diagnostics: [BuildDiagnostic!]! # everything the final build reported
errors: [BuildDiagnostic!]! # the diagnostics with type Error
appliedPatches: [BuildPatch!]! # changes the API made to your files for the compiler
patches: [BuildPatch!]! # changes still required; empty unless Canceled
files: [PenFile!]! # the compiled output; empty unless Success
version: PenVersion # null while that version is still being put together
next: Build # the build of the version applying this one's patches, if any
previous: Build # the build of the version you wrote, if this one came from it
}
type BuildDiagnostic {
type: DiagnosticType! # Error | Warning | Information | Hint
scope: DiagnosticScope! # Build | BuildStep | File
message: String!
code: String
url: String # documentation for the diagnostic, when there is some
blockName: String
path: String # the file, when the diagnostic is about one
fileID: ID
range: FileRange # start and end { line column }, starting at 1
}
union BuildPatch = BuildPatchCreate | BuildPatchUpdate | BuildPatchDelete
type BuildPatchCreate {
path: String!
content: String!
}
type BuildPatchUpdate {
fileID: ID!
path: String!
content: String!
}
type BuildPatchDelete {
fileID: ID!
path: String!
}
type PenDeployment {
id: ID!
versionID: ID!
url: String!
customDomains: [String!]!
createdAt: Time!
updatedAt: Time!
}
type PenSettings {
autoDeploy: Boolean!
autoRun: Boolean!
autoSave: Boolean!
indentWith: String! # "tabs" | "spaces"
tabSize: Int! # 1 to 6
}
type Collaborator {
username: String!
name: String!
role: Role! # Owner | Editor | Viewer
isTeamMember: Boolean!
}
type CollaboratorInvite {
id: ID!
email: String # one of email and username is set
username: String
role: Role!
createdAt: Time!
}
type Collection {
id: ID!
title: String!
description: String!
access: AccessLevel! # Public | Private
itemsCount: Int!
popularity: Int!
createdAt: Time!
updatedAt: Time!
url: String!
pens(input: PaginationInput): PenPage! # your own Pens in it
}
type Asset {
id: ID!
filename: String!
url: String! # where the file is served
isBinary: Boolean!
content: String # a text Asset's content; null for binary Assets
createdAt: Time!
updatedAt: Time!
}Code language: GraphQL (graphql)
PenFile.content and Asset.content are read from storage when selected, so lists are cheapest without them. A binary file has no content; its bytes are served at its url.
The payloads of the mutations that build:
type PenFilesWritePayload { # penFilesWrite, penFileWrite, penFileDelete
pen: Pen!
version: PenVersion! # the newest version the write produced
build: Build! # that version's build
}
type PenPayload {
pen: Pen!
build: Build # set by penCreate and penFork
}Code language: GraphQL (graphql)
Privacy and Passwords
penCreate, penFork and penUpdate take access and password. Anything but Public needs a PRO plan (otherwise FORBIDDEN). Protected needs a password of at least 8 characters; a fork of a Protected Pen is Protected too, so it needs one as well. On a Pen that is already Protected, penUpdate with password alone replaces the password. A password with any other access is BAD_USER_INPUT. The password is write-only: Pen.password exists so the field reads naturally in a client’s types, but it is always null.
mutation {
penUpdate(
input: {
id: "01a08738-73f0-7847-b6a8-5789d289c158"
access: Protected
password: "correct horse battery"
}
) {
pen {
access
password # null
}
}
}Code language: GraphQL (graphql)
Files
Files belong to 2.0 Pens; a Classic Pen’s files can be read but not written. Paths are full paths like /src/app.js (a leading / is added if missing). penFilesWrite takes files: [PenFileChangeInput!]!, applied in order. Each change addresses a file by path or by id and says what to do with it:
| Change | Effect |
|---|---|
{ path, content } | Writes the file at path, creating it if needed. |
{ id, content } | Writes the file with that ID, wherever it is. |
{ id, path } | Moves the file with that ID to path. It keeps its ID. Add content to write there too. |
{ path, delete: true } | Deletes the file, or every file under a directory path. |
{ id, delete: true } | Deletes the file with that ID. |
A change writes either content (text) or upload (a file sent as a multipart part, see Uploads), not both. Reading back, PenFile.content is null for binary files, and every 2.0 file, text or binary, is served at its url.
Pass expectedVersionID (the savedVersion.id your changes were built on) and the write is refused with CONFLICT if the Pen’s head has moved since. The API derives content hashes and the version hash itself; you never send them.
Every write is built, and the build may change the files you wrote. Read the next section before making more than one write to a Pen.
Builds
Pens are compiled, so a write works like a push to a repository with CI attached. Every write (penFilesWrite, penFileWrite, penFileDelete, penCreate, penFork) saves your files as a new version and builds it. The payload carries the newest version the write produced and its build.
When the Compiler Asks for Changes
The compiler sometimes needs the source files to change before it can build them. Adding a .ts file for the first time calls for a /tsconfig.json. An index.html that links to a JavaScript file using ES Modules needs type="module" on its <script> tag. An imported package needs to be in package.json. The Pen’s config file has to match the kinds of files in the Pen. A Pen that formats on save gets formatted.
These changes are patches. The API applies them for you, builds again, and saves the result as one more version, however many builds that takes (at most five). So a write produces one version, or two when the compiler asked for changes:
- The version holding exactly the files you sent. Its build ends
Canceled, withnextpointing at the second version’s build. - The version holding your files with the patches applied. It becomes the Pen’s head. Its build’s
appliedPatcheslist what changed, and itspreviouspoints back to the first.
A patch can create a file, and it can also change or delete a file you just wrote. After a write, the Pen’s files may not be the files you sent.
Waiting and Polling
A write waits for its build before answering. wait, a field of each of those mutations’ input, is the number of seconds: 45 by default, at most 75, and 0 to answer as soon as your files are saved. The build carries on whether or not the request is still waiting for it.
When the payload comes back, look at build:
build.status | What to do |
|---|---|
Success | Done. version.files are the source files and build.files the compiled output. |
Failed | Done, with errors. Your files are saved; build.errors say why they don’t build. Nothing is retried. Fix the files and write again. |
Queued, Running | Not done. Poll build(id) with build.id. |
Canceled with next | The API is applying the compiler’s changes. Poll build(id) with next.id; that build is Running until its version is saved. |
Canceled without next | Done, but changes are still required: a patch could not be applied, or five builds weren’t enough. build.patches lists them, and build.diagnostics says what happened. Apply them yourself and write again. |
Writing More Than Once
Before you write to the same Pen again:
- Wait until the build is done, following
nextif there is one. - Read the files again, from the finished build’s
version { id files { ... } }or frompen(id) { savedVersion { id } files { ... } }. Don’t reuse your own copy of what you sent. - Send that version’s ID as
expectedVersionIDwith the next write.
A write made while the previous one is still building is based on files that are about to change. With expectedVersionID it is refused with CONFLICT once the head has moved. Without it, the write can undo a change the compiler made, and the next build has to make it again.
Source Files and Output Files
PenVersion.filesare the source files (thinksrc): what you read, edit and write back. Theirurlserves the file raw.Build.filesare the compiled output (thinkdist): whatpreviewUrland a deployment serve. Theirurlis under the version’spreviewUrl. They exist only when the build succeeded.
The Examples below include a full write, poll and re-read.
Uploads
Binary files go up as multipart parts, following the GraphQL multipart request spec that most GraphQL clients implement. Any input with an upload: Upload field takes one: PenFileInput (penCreate), PenFileChangeInput (penFilesWrite), PenFileWriteInput (penFileWrite), AssetCreateInput and AssetUpdateInput. The request is multipart/form-data with three kinds of field:
operations: the JSON body, withnullwhere each file goes.map: which part fills which variable path.- One part per file, named as in
map.
curl https://api.codepen.io/v2/graphql \
-H "Authorization: Bearer $KEY" \
-F operations='{ "query": "mutation ($id: ID!, $file: Upload!) { penFileWrite(input: { penID: $id, path: \"img/logo.png\", upload: $file }) { version { id } } }", "variables": { "id": "01a08738-73f0-7847-b6a8-5789d289c158", "file": null } }' \
-F map='{ "0": ["variables.file"] }' \
-F 0=@logo.pngCode language: Bash (bash)
The whole request is capped at 120 MB and one file at 30 MB; your plan’s own limit applies on save. An empty part, or content and upload together, answer BAD_USER_INPUT. Text may go either way: as content in a plain JSON request, or as an upload.
Filters
pens and collections take filters beside pagination. Every filter given must match, and totalCount counts under the same filters.
input PensInput {
access: [AccessLevel!] # any of Public | Private | Protected | Collaborators
deployed: Boolean
template: Boolean
forks: PenForks # All (default) | Forked | NotForked
editor: PenEditor # Classic | V2
tags: [String!] # tagged with any of these; up to three
searchTerm: String # matches the title or description
pagination: PaginationInput
}
input CollectionsInput {
access: [AccessLevel!] # any of Public | Private
pagination: PaginationInput
}Code language: GraphQL (graphql)
Tags are matched the way they are saved: lowercased, spaces as dashes, punctuation dropped ("Two Words!" is two-words). searchTerm uses web search syntax: all words have to appear, "quoted words" stay together and -word excludes one. It is at most 200 characters.
Pagination
Every list takes the same input and answers the same page shape: the connection shape of GitHub’s GraphQL API, without edges.
input PaginationInput {
cursor: String # a page's endCursor (next page) or startCursor (previous page)
limit: Int # default 20, maximum 100
sortBy: SortBy # CreatedAt (default) | UpdatedAt | Popularity (Pens and Collections)
sortOrder: SortOrder # Asc | Desc, default Desc
}
type PenPage { # likewise CollectionPage, PenVersionPage, AssetPage
nodes: [Pen!]!
pageInfo: PageInfo!
totalCount: Int! # the whole list, not only this page
}
type PageInfo {
startCursor: String
endCursor: String
hasPreviousPage: Boolean!
hasNextPage: Boolean!
}Code language: GraphQL (graphql)
Pass a page’s endCursor as cursor for the page after it, or its startCursor for the page before it. A limit over 100 is refused. Lists sort by CreatedAt descending (newest first) unless told otherwise; pens and collections also sort by Popularity. Keep the same sortBy and sortOrder while following a list’s cursors, since a cursor only makes sense in the sort that produced it.
query {
pens(input: { pagination: { limit: 20, cursor: "<endCursor>" } }) {
totalCount
nodes {
id
title
}
pageInfo {
startCursor
endCursor
hasPreviousPage
hasNextPage
}
}
}Code language: GraphQL (graphql)
Here is the whole loop in JavaScript:
async function fetchAllPens(apiKey) {
const query = `query ($cursor: String) {
pens(input: { pagination: { limit: 100, cursor: $cursor } }) {
nodes { id title }
pageInfo { endCursor hasNextPage }
}
}`;
const pens = [];
let cursor = null;
do {
const response = await fetch("https://api.codepen.io/v2/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ query, variables: { cursor } })
});
if (!response.ok) throw new Error(`CodePen API: ${response.status}`);
const { data, errors } = await response.json();
if (errors) throw new Error(errors[0].message);
pens.push(...data.pens.nodes);
cursor = data.pens.pageInfo.hasNextPage ? data.pens.pageInfo.endCursor : null;
} while (cursor);
return pens;
}Code language: JavaScript (javascript)
Errors
Errors come back in errors[] with the code in extensions.code. The codes are listed on the API v2 page. A refused key (401) and the rate limit (429) are answered before the query runs, with the body { "message", "code" }. A CONFLICT also carries expectedVersionID and currentVersionID:
{
"errors": [
{
"message": "the Pen's head is 01a0...c5, not 01a0...99; read it again and retry",
"path": ["penFilesWrite"],
"extensions": {
"code": "CONFLICT",
"expectedVersionID": "01a0...99",
"currentVersionID": "01a0...c5"
}
}
],
"data": null
}Code language: JSON / JSON with Comments (json)
An invalid query itself, including an unknown enum value, answers GRAPHQL_VALIDATION_FAILED.
A build that fails is not an error: the mutation succeeds with your files saved, and the payload’s build.status is Failed.
Examples
Every example is a complete operation for POST /v2/graphql. IDs are illustrative.
Read a Pen and Its Files
query ($id: ID!) {
pen(id: $id) {
id
title
access
editor
urls {
editor
preview
}
screenshotURLs {
medium
}
savedVersion {
id
build {
status
}
}
files {
id
path
content
}
}
}Code language: GraphQL (graphql)
{ "id": "01a08738-73f0-7847-b6a8-5789d289c158" }Code language: JSON / JSON with Comments (json)
List Pens
Public 2.0 Pens that are not forks, tagged gsap or svg, mentioning “cards” in their title or description:
query {
pens(
input: {
access: [Public]
editor: V2
forks: NotForked
tags: ["gsap", "svg"]
searchTerm: "cards"
pagination: { limit: 50 }
}
) {
totalCount
nodes {
id
title
updatedAt
}
pageInfo {
endCursor
hasNextPage
}
}
}Code language: GraphQL (graphql)
Create a Pen
You get starter files when files is omitted. The title defaults to “Untitled” and access to your default privacy setting.
mutation {
penCreate(
input: {
title: "Hello"
description: "Made with the API"
tags: ["api", "demo"]
access: Private
files: [
{ path: "index.html", content: "<h1>Hello</h1>" }
{ path: "style.css", content: "h1 { color: teal }" }
]
}
) {
pen {
id
urls {
editor
preview
}
savedVersion {
id
}
files {
id
path
}
}
build {
id
status
rounds
errors {
path
message
}
}
}
}Code language: GraphQL (graphql)
build is the build of the new Pen’s newest version. The new Pen is built like any other write, so the files in it may be more than the files you sent (see Builds).
Update a Pen
Only the fields given change. tags replaces the whole list.
mutation {
penUpdate(
input: {
id: "01a08738-73f0-7847-b6a8-5789d289c158"
title: "Hello, world"
tags: ["api", "demo", "v2"]
access: Public
}
) {
pen {
id
title
tags
access
}
}
}Code language: GraphQL (graphql)
Write Several Files as One Version
Move script.js to src/app.js by its ID and give it new content, rewrite index.html by path, and delete style.css, all in one saved version.
mutation {
penFilesWrite(
input: {
penID: "01a08738-73f0-7847-b6a8-5789d289c158"
expectedVersionID: "01a08738-73f0-7848-b8bf-e8e7e91b1ae2"
files: [
{
id: "01a0884b-3177-71c3-a1c2-9452bfb9a3fb"
path: "src/app.js"
content: "console.log('moved')"
}
{ path: "index.html", content: "<script src=\"src/app.js\"></script>" }
{ path: "style.css", delete: true }
]
}
) {
version {
id
files {
id
path
content
}
}
build {
id
status
rounds
errors {
path
message
}
appliedPatches {
... on BuildPatchCreate {
path
}
... on BuildPatchUpdate {
path
}
... on BuildPatchDelete {
path
}
}
next {
id
status
}
}
}
}Code language: GraphQL (graphql)
A stale expectedVersionID answers CONFLICT. version is the newest version the write produced, and build is its build. When build.status is still Queued or Running, or Canceled with a next, poll until it is done (next example).
Write, Then Wait for the Build
Add a TypeScript file to a Pen that has none. With wait: 0 the write answers as soon as the file is saved, with the build still to run:
mutation {
penFileWrite(
input: {
penID: "01a08738-73f0-7847-b6a8-5789d289c158"
path: "src/app.ts"
content: "const n: number = 1;"
expectedVersionID: "01a08738-73f0-7848-b8bf-e8e7e91b1ae2"
wait: 0
}
) {
build {
id
status
}
}
}Code language: GraphQL (graphql)
{
"data": {
"penFileWrite": {
"build": {
"id": "01a08853-f1ab-7967-812e-eda134bd9e61",
"status": "Queued"
}
}
}
}Code language: JSON / JSON with Comments (json)
Poll the build:
query {
build(id: "01a08853-f1ab-7967-812e-eda134bd9e61") {
status
rounds
next {
id
status
}
}
}Code language: GraphQL (graphql)
The compiler asked for a /tsconfig.json, so this build ends Canceled, and next says the API is applying the change:
{
"data": {
"build": {
"status": "Canceled",
"rounds": 1,
"next": {
"id": "01a08854-2c10-7c7e-9b1a-3f0d5a6b7c8d",
"status": "Running"
}
}
}
}Code language: JSON / JSON with Comments (json)
Poll next.id until it is done. One query then gives you what was changed, the compiled output, and the source files to base the next write on:
query {
build(id: "01a08854-2c10-7c7e-9b1a-3f0d5a6b7c8d") {
status
rounds
errors {
path
message
}
appliedPatches {
... on BuildPatchCreate {
path
}
... on BuildPatchUpdate {
path
}
... on BuildPatchDelete {
path
}
}
files {
# the compiled output
path
url
}
version {
id # the next write's expectedVersionID
files {
# the source files, as they are now
id
path
content
}
}
previous {
id
}
}
}Code language: GraphQL (graphql)
Here is the whole loop in JavaScript:
async function graphql(apiKey, query, variables) {
const response = await fetch("https://api.codepen.io/v2/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ query, variables })
});
if (!response.ok) throw new Error(`CodePen API: ${response.status}`);
const { data, errors } = await response.json();
if (errors) throw new Error(errors[0].message);
return data;
}
const BUILD = `
id
status
errors { path message }
next { id }
version { id files { id path content } }
`;
async function writeFiles(apiKey, penID, expectedVersionID, files) {
const written = await graphql(
apiKey,
`mutation ($input: PenFilesWriteInput!) {
penFilesWrite(input: $input) { build { ${BUILD} } }
}`,
{ input: { penID, expectedVersionID, files } }
);
let build = written.penFilesWrite.build;
// Follow the build until it is done, through next when the API is
// applying the compiler's changes.
for (;;) {
const running = build.status === "Queued" || build.status === "Running";
const next = build.status === "Canceled" ? build.next : null;
if (!running && !next) break;
await new Promise((resolve) => setTimeout(resolve, 2000));
const polled = await graphql(
apiKey,
`query ($id: ID!) { build(id: $id) { ${BUILD} } }`,
{ id: next ? next.id : build.id }
);
build = polled.build;
}
// build.version.files are the source files as they are now, which may
// not be the files sent.
return build;
}Code language: JavaScript (javascript)
Pass the returned version.id as expectedVersionID the next time, and build the next changes from version.files.
Write One File
mutation {
penFileWrite(
input: {
penID: "01a08738-73f0-7847-b6a8-5789d289c158"
path: "src/app.js"
content: "console.log(1)"
expectedVersionID: "01a08738-73f0-7848-b8bf-e8e7e91b1ae2"
}
) {
version {
id
files {
id
path
}
}
build {
id
status
}
}
}Code language: GraphQL (graphql)
Delete One File
By ID, or by path (a directory path deletes every file under it).
mutation {
penFileDelete(
input: {
penID: "01a08738-73f0-7847-b6a8-5789d289c158"
id: "01a0884b-3177-71c3-a1c2-9452bfb9a3fb"
}
) {
version {
id
files {
path
}
}
build {
id
status
}
}
}Code language: GraphQL (graphql)
Upload a Binary File
Send the file as a multipart part (see Uploads) into the upload field of penFilesWrite, penFileWrite or penCreate. The operation, as it appears in the operations field:
mutation ($id: ID!, $file: Upload!) {
penFileWrite(input: { penID: $id, path: "img/logo.png", upload: $file }) {
version {
id
files {
id
path
isBinary
url
}
}
}
}Code language: GraphQL (graphql)
{ "id": "01a08738-73f0-7847-b6a8-5789d289c158", "file": null }Code language: JSON / JSON with Comments (json)
with map { "0": ["variables.file"] } and the file as part 0. Reading it back, content is null and the bytes are at url:
{
"path": "/img/logo.png",
"isBinary": true,
"content": null,
"url": "https://01a08738-73f0-7847-b6a8-5789d289c158.codepenusercontent.com/img/logo.png"
}Code language: JSON / JSON with Comments (json)
Deploy and Undeploy
versionID defaults to the saved head.
mutation {
penDeploy(input: { penID: "01a08738-73f0-7847-b6a8-5789d289c158" }) {
pen {
deployment {
id
versionID
url
customDomains
}
}
}
}Code language: GraphQL (graphql)
mutation {
penUndeploy(input: { penID: "01a08738-73f0-7847-b6a8-5789d289c158" }) {
pen {
deployment {
# null once undeployed
id
}
}
}
}Code language: GraphQL (graphql)
Update Settings
mutation {
penSettingsUpdate(
input: {
penID: "01a08738-73f0-7847-b6a8-5789d289c158"
autoDeploy: true
indentWith: "tabs"
tabSize: 4
}
) {
pen {
settings {
autoDeploy
autoRun
autoSave
indentWith
tabSize
}
}
}
}Code language: GraphQL (graphql)
Fork and Delete a Pen
A fork copies one of your 2.0 Pens and keeps the original’s title, description, tags and access unless given. The copy is built, and the payload’s build is that build. A delete moves the Pen to the trash. You can restore it from Deleted Items on the site for 3 days.
mutation {
penFork(
input: { id: "01a08738-73f0-7847-b6a8-5789d289c158", title: "My copy" }
) {
pen {
id
title
access
}
build {
id
status
}
}
}Code language: GraphQL (graphql)
mutation {
penDelete(input: { id: "01a08738-73f0-7847-b6a8-5789d289c158" }) {
pen {
id
title
}
}
}Code language: GraphQL (graphql)
Versions: List, Inspect, Restore
query {
pen(id: "01a08738-73f0-7847-b6a8-5789d289c158") {
versions(input: { limit: 10 }) {
nodes {
id
createdAt
build {
status
}
deployed
previewUrl
}
pageInfo {
endCursor
hasNextPage
}
}
}
}Code language: GraphQL (graphql)
One version, with its build, its compiled output and its source files:
query {
penVersion(id: "01a08738-73f0-7848-b8bf-e8e7e91b1ae2") {
id
penID
build {
status
startedAt
endedAt
diagnostics {
type
path
message
}
files {
path
url
}
}
files {
path
content
}
}
}Code language: GraphQL (graphql)
mutation {
penVersionRestore(
input: {
penID: "01a08738-73f0-7847-b6a8-5789d289c158"
versionID: "01a08738-73f0-7848-b8bf-e8e7e91b1ae2"
}
) {
pen {
savedVersion {
id
}
}
}
}Code language: GraphQL (graphql)
A restored version keeps the build of the version it copies; it is not built again.
Collaborators
mutation {
collaboratorInvite(
input: {
penID: "01a08738-73f0-7847-b6a8-5789d289c158"
emailOrUsername: "teammate"
role: Editor
}
) {
pen {
collaborators {
username
role
isTeamMember
}
collaboratorInvites {
id
username
email
role
}
}
}
}Code language: GraphQL (graphql)
mutation {
collaboratorUpdateRole(
input: {
penID: "01a08738-73f0-7847-b6a8-5789d289c158"
username: "teammate"
role: Viewer
}
) {
pen {
collaborators {
username
role
}
}
}
}Code language: GraphQL (graphql)
mutation {
collaboratorRemove(
input: {
penID: "01a08738-73f0-7847-b6a8-5789d289c158"
username: "teammate"
}
) {
pen {
collaborators {
username
}
}
}
}Code language: GraphQL (graphql)
Collections
mutation {
collectionCreate(input: { title: "Demos", access: Public }) {
collection {
id
title
url
}
}
}Code language: GraphQL (graphql)
mutation {
collectionAddPen(
input: {
collectionID: "QbaoZL"
penID: "01a08738-73f0-7847-b6a8-5789d289c158"
}
) {
collection {
itemsCount
pens(input: { limit: 20 }) {
nodes {
id
title
}
}
}
}
}Code language: GraphQL (graphql)
Deleting a Collection moves the Collection itself to the trash; the Pens in it are untouched. Like a Pen, you can restore it from Deleted Items on the site for 3 days.
mutation {
collectionDelete(input: { id: "QbaoZL" }) {
collection {
id
title
}
}
}Code language: GraphQL (graphql)
Assets
Assets are the files in your Asset Hosting, served from their url. A file goes up as an upload (a multipart part, see Uploads). A text Asset (SVG, HTML, CSS, Sass, JS, TS, JSON, Markdown, plain text and similar) may be sent as content instead. Send one or the other, not both.
mutation ($file: Upload!) {
assetCreate(input: { filename: "logo.png", upload: $file }) {
asset {
id
isBinary
url
}
}
}Code language: GraphQL (graphql)
with variables { "file": null }, map { "0": ["variables.file"] } and the file as part 0.
query {
assets(input: { pagination: { sortBy: CreatedAt, limit: 50 } }) {
nodes {
id
filename
url
isBinary
updatedAt
}
pageInfo {
endCursor
hasNextPage
}
}
}Code language: GraphQL (graphql)
mutation {
assetUpdate(
input: {
id: "k7Ym0R"
filename: "theme.css"
content: "body { color: red }"
}
) {
asset {
filename
url
}
}
}Code language: GraphQL (graphql)
A rename changes the Asset’s url. The new name must be unique among your Assets and cannot contain / or \. New content counts against your plan’s per-file size and total storage (FORBIDDEN when over), and content is only for text Assets. assetDelete(input: { id }) removes the file from storage and returns the Asset as it was.