Coverpad
Download on the App Store

For developers

Send a layout.
Coverpad opens it.

Coverpad accepts .coverlayout JSON — text, shapes, and photo slots, no embedded images. Hand it off with a custom URL or a shared file. The user confirms, then gets an editable .coverpad document.

Which handoff to use

  • URL scheme — best for small, generated layouts from another iOS app, Shortcut, or web page (JSON up to 512 KB).
  • File — best for large layouts, downloads, AirDrop, or anything the user should keep as a file.

Do not send a .coverpad package as a public API — that format is internal and includes assets. Always use coverlayout.

URL scheme

Open Coverpad with base64url-encoded UTF-8 JSON in the query string. Coverpad shows a confirmation alert before creating the document.

coverpad://import?layout=<base64url>&name=<optional>
ParameterDescription
layoutRequired. Base64url (or standard base64) of UTF-8 coverlayout JSON. Max 512 KB decoded.
nameOptional. Suggested document base name (path separators stripped; max 64 characters).

iOS sample project

Download a minimal Xcode app that builds a Hello World! coverlayout (gradient background + centered title) and opens Coverpad withcoverpad://import. Install Coverpad on the same simulator or device, run the sample, tapOpen in Coverpad, then confirm.

Download coverpad-handoff-ios.zip

Swift

import UIKit

func openInCoverpad(layoutJSON: Data, name: String? = nil) {
  let encoded = layoutJSON.base64EncodedString()
    .replacingOccurrences(of: "+", with: "-")
    .replacingOccurrences(of: "/", with: "_")
    .replacingOccurrences(of: "=", with: "")

  var components = URLComponents()
  components.scheme = "coverpad"
  components.host = "import"
  var items = [URLQueryItem(name: "layout", value: encoded)]
  if let name {
    items.append(URLQueryItem(name: "name", value: name))
  }
  components.queryItems = items

  guard let url = components.url else { return }
  // Optional: declare LSApplicationQueriesSchemes = coverpad to use canOpenURL
  UIApplication.shared.open(url)
}

JavaScript / Shortcuts

Build the same URL and open it. In Shortcuts, use Open URLs with the constructed string. From another app’s Info.plist, add coverpad underLSApplicationQueriesSchemes if you call canOpenURL.

const json = JSON.stringify(layout);
const base64url = btoa(unescape(encodeURIComponent(json)))
  .replace(/\+/g, "-")
  .replace(/\//g, "_")
  .replace(/=+$/, "");
const url = `coverpad://import?layout=${base64url}&name=${encodeURIComponent("My Cover")}`;
window.location.href = url;

File handoff

Write UTF-8 JSON to a file named Something.coverlayout(UTI ca.maysoft.coverlayout). Then:

  • Present UIActivityViewController or UIDocumentInteractionController so the user can Open in Coverpad
  • AirDrop the file to a device with Coverpad installed
  • Save to Files and open from there

Coverpad imports the stub into a new editable document. It never edits the .coverlayout file in place.

Format

  • JSON snapshot: formatVersion, pageFormat, elements
  • Element kinds: text and shapes (including photo-slot placeholders). No images or icons inside the file
  • Max file / payload size: 512 KB
  • Also useful for humans: Create with AI prompt for drafting layouts

Minimal example

{
  "formatVersion": 1,
  "pageFormat": "instagramPost",
  "elements": [
    {
      "id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "center": [540, 200],
      "content": {
        "kind": "text",
        "text": "Hello",
        "font": { "name": "Helvetica", "size": 48 }
      }
    }
  ]
}

JSON Schema

Same file as in the app and repo docs. Validate before you send.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://maysoft.ca/schemas/coverlayout.schema.json",
  "title": "Coverpad coverlayout",
  "description": "JSON-only Coverpad interchange format (text + shapes). No package assets.",
  "type": "object",
  "additionalProperties": false,
  "required": ["formatVersion", "pageFormat", "elements"],
  "properties": {
    "formatVersion": {
      "type": "integer",
      "const": 1
    },
    "pageFormat": {
      "oneOf": [
        {
          "type": "string",
          "enum": ["a4", "letter", "youtubeThumbnail", "instagramPost", "story"]
        },
        {
          "type": "object",
          "additionalProperties": false,
          "required": ["kind", "width", "height"],
          "properties": {
            "kind": { "const": "custom" },
            "width": { "type": "number", "minimum": 150, "maximum": 3000 },
            "height": { "type": "number", "minimum": 150, "maximum": 3000 }
          }
        }
      ]
    },
    "backgroundColor": { "$ref": "#/$defs/color" },
    "backgroundGradient": { "$ref": "#/$defs/textGradient" },
    "elements": {
      "type": "array",
      "maxItems": 150,
      "items": { "$ref": "#/$defs/element" }
    }
  },
  "$defs": {
    "color": {
      "type": "object",
      "additionalProperties": false,
      "required": ["r", "g", "b", "a"],
      "properties": {
        "r": { "type": "number", "minimum": 0, "maximum": 1 },
        "g": { "type": "number", "minimum": 0, "maximum": 1 },
        "b": { "type": "number", "minimum": 0, "maximum": 1 },
        "a": { "type": "number", "minimum": 0, "maximum": 1 }
      }
    },
    "point": {
      "type": "array",
      "minItems": 2,
      "maxItems": 2,
      "items": { "type": "number" }
    },
    "size": {
      "type": "array",
      "minItems": 2,
      "maxItems": 2,
      "items": { "type": "number", "minimum": 1, "maximum": 4000 }
    },
    "textGradient": {
      "type": "object",
      "description": "Linear gradient for canvas background, text fill, or shape fill. Max 8 colors. angleRadians: 0 = L→R, π/2 = top→bottom.",
      "additionalProperties": false,
      "required": ["colors", "angleRadians"],
      "properties": {
        "colors": {
          "type": "array",
          "minItems": 1,
          "maxItems": 8,
          "items": { "$ref": "#/$defs/color" }
        },
        "locations": {
          "type": "array",
          "maxItems": 8,
          "items": { "type": "number", "minimum": 0, "maximum": 1 }
        },
        "angleRadians": { "type": "number" }
      }
    },
    "shadow": {
      "type": "object",
      "description": "Drop shadow. offset is [x,y] points; blurRadius ≥ 0.",
      "additionalProperties": false,
      "required": ["color", "offset", "blurRadius"],
      "properties": {
        "color": { "$ref": "#/$defs/color" },
        "offset": { "$ref": "#/$defs/point" },
        "blurRadius": { "type": "number", "minimum": 0 }
      }
    },
    "element": {
      "type": "object",
      "additionalProperties": false,
      "required": ["id", "center", "content"],
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid"
        },
        "center": { "$ref": "#/$defs/point" },
        "content": {
          "oneOf": [
            { "$ref": "#/$defs/textContent" },
            { "$ref": "#/$defs/shapeContent" }
          ]
        }
      }
    },
    "textContent": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "text", "font"],
      "properties": {
        "kind": { "const": "text" },
        "text": { "type": "string", "maxLength": 512 },
        "font": {
          "type": "object",
          "additionalProperties": false,
          "required": ["name", "size"],
          "properties": {
            "name": { "type": "string", "minLength": 1 },
            "size": { "type": "number", "minimum": 8, "maximum": 400 }
          }
        },
        "style": { "$ref": "#/$defs/labelStyle" }
      }
    },
    "shapeContent": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "shapeKind", "style"],
      "properties": {
        "kind": { "const": "shape" },
        "shapeKind": {
          "type": "string",
          "enum": [
            "rectangle",
            "circle",
            "oval",
            "pill",
            "triangle",
            "octagon",
            "heart",
            "star",
            "conversation",
            "arrow"
          ]
        },
        "style": { "$ref": "#/$defs/shapeStyle" }
      }
    },
    "labelStyle": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "textColor": { "$ref": "#/$defs/color" },
        "shadow": { "$ref": "#/$defs/shadow" },
        "outline": { "$ref": "#/$defs/outline" },
        "gradient": { "$ref": "#/$defs/textGradient" },
        "decoration": {
          "type": "object",
          "additionalProperties": false,
          "required": ["underline", "strikethrough"],
          "properties": {
            "underline": { "type": "boolean" },
            "strikethrough": { "type": "boolean" },
            "color": { "$ref": "#/$defs/color" }
          }
        },
        "background": {
          "type": "object",
          "additionalProperties": false,
          "required": ["color", "cornerRadius"],
          "properties": {
            "color": { "$ref": "#/$defs/color" },
            "cornerRadius": { "type": "number", "minimum": 0 }
          }
        },
        "rotationRadians": { "type": "number" },
        "textAlignment": { "type": "string", "enum": ["left", "center", "right"] },
        "opacity": { "type": "number", "minimum": 0.1, "maximum": 1 },
        "isBold": { "type": "boolean" },
        "isItalic": { "type": "boolean" }
      }
    },
    "shapeStyle": {
      "type": "object",
      "additionalProperties": false,
      "required": ["displaySize"],
      "properties": {
        "displaySize": { "$ref": "#/$defs/size" },
        "fillColor": { "$ref": "#/$defs/color" },
        "gradient": { "$ref": "#/$defs/textGradient" },
        "opacity": { "type": "number", "minimum": 0.1, "maximum": 1 },
        "outline": { "$ref": "#/$defs/outline" },
        "shadow": { "$ref": "#/$defs/shadow" },
        "rotationRadians": { "type": "number" },
        "isPlaceholder": { "type": "boolean" },
        "placeholderText": { "type": "string", "maxLength": 35 }
      }
    },
    "outline": {
      "type": "object",
      "additionalProperties": false,
      "required": ["color", "width"],
      "properties": {
        "color": { "$ref": "#/$defs/color" },
        "width": { "type": "number", "exclusiveMinimum": 0 }
      }
    }
  }
}