Skip to content
Locanda

Back to Help

Write a custom spells file

The form is fine for a few spells. For many, write a file and import it in Settings, Backup tab: by hand, with a script or with an AI assistant. This page has everything the file needs.

What the file is

A JSON file in the Locanda format with your spells in customSpells, no spellbooks and no settings: the same kind of file that “Export custom spells” saves.

It holds at most 1,000 spells and 5 MB.

Import is all or nothing: if one spell breaks a rule, nothing is added, and the import lists what to fix and where. For a spell whose id is already on this device, you choose whether to replace it or keep both.

An example

A whole file with one cantrip, written in English and Italian. Copy it as a starting point.

{
  "format": "locanda",
  "schemaVersion": 1,
  "exportedAt": "2026-10-25T12:00:00Z",
  "appVersion": "0.0.0",
  "dataVersion": "custom",
  "spellbooks": [],
  "customSpells": [
    {
      "id": "custom:6c49a5ff-2c31-4316-b0ce-0ebd82a9d04d",
      "source": "custom",
      "level": 0,
      "school": "illusion",
      "classes": [
        "bard",
        "sorcerer",
        "wizard"
      ],
      "castingTime": {
        "unit": "action",
        "amount": 1
      },
      "ritual": false,
      "range": {
        "type": "ranged",
        "distance": 30,
        "unit": "feet",
        "area": null
      },
      "components": {
        "v": false,
        "s": true,
        "m": null
      },
      "duration": {
        "type": "timed",
        "amount": 1,
        "unit": "hour"
      },
      "concentration": false,
      "damageTypes": [],
      "savingThrows": [],
      "text": {
        "en": {
          "name": "Lantern Moth",
          "castingCondition": null,
          "material": null,
          "description": "A moth of soft light appears at a point you can see within range. It sheds dim light in a 10-foot radius and follows the path you trace with one finger.",
          "higherLevels": null,
          "cantripUpgrade": "You create one more moth when you reach levels 5, 11 and 17."
        },
        "it": {
          "name": "Falena lanterna",
          "castingCondition": null,
          "material": null,
          "description": "Una falena di luce tenue appare in un punto entro gittata che puoi vedere. Emana luce fioca in un raggio di 3 metri e segue il percorso che tracci con un dito.",
          "higherLevels": null,
          "cantripUpgrade": "Crei una falena in più quando raggiungi il 5°, l'11° e il 17° livello."
        }
      },
      "reference": null,
      "tags": [
        "Homebrew"
      ],
      "createdAt": "2026-10-25T12:00:00Z",
      "updatedAt": "2026-10-25T12:00:00Z"
    }
  ]
}

The fields

Write every field in the tables, in any order. Where a field has nothing to say, write null or [] as the table says; never leave it out. Fields that are not in the tables are refused. Line breaks and tabs are allowed only in the Markdown fields.

The file

FieldWhat it holds
formatAlways "locanda".
schemaVersionAlways 1.
exportedAtWhen the file was made: a date and time in RFC 3339, such as 2026-10-25T12:00:00Z.
appVersionThe version of the app that wrote the file, such as 1.0.0. A file you write can use 0.0.0.
dataVersionLowercase letters, digits, dots and hyphens, such as custom.
spellbooksAlways []: a custom spells file has no spellbooks.
customSpellsThe spells: from 1 to 1,000, each with its own id.

Each spell

FieldWhat it holds
idcustom: followed by a new random UUID (version 4, lowercase), such as custom:6c49a5ff-2c31-4316-b0ce-0ebd82a9d04d. Make a new one for every spell: the same id means the same spell.
sourceAlways "custom".
levelA whole number from 0 (a cantrip) to 9.
schoolOne of the schools below.
classesA list of the classes below, at least one, each once, such as ["wizard"].
castingTime.unitOne of the casting time units below.
castingTime.amountHow many actions, minutes or hours: a whole number from 1 to 1,000. Use 1 for an action, a bonus action or a reaction.
ritualtrue or false: it can be cast as a ritual.
range.typeOne of the range types below.
range.distanceFor a ranged spell, the distance: a number above 0, up to 1,000,000. For every other range type, null.
range.unitFor a ranged spell, feet or miles; for every other range type, null. Distances are in feet, never metres: the app converts them.
range.areanull, or the area of effect: an object with shape (one of the area shapes below), size (a number above 0) and unit (feet or miles).
components.vtrue or false: it has a verbal component.
components.strue or false: it has a somatic component.
components.mnull with no material component; otherwise an object with costGp (its cost in gold pieces, 0 to 1,000,000, or null) and consumed (true or false). The material’s words go in text.
duration.typeOne of the duration types below.
duration.amountFor a timed duration, a whole number from 1 to 1,000; otherwise null.
duration.unitFor a timed duration, one of the duration units below; otherwise null.
concentrationtrue or false: it needs concentration.
damageTypesA list of the damage types below, each once; [] for none.
savingThrowsA list of the abilities below that the spell calls for saving throws with, each once; [] for none.
textThe spell’s words by language: an object with en, it or both, each holding the fields below. Where a language is missing, the app shows the other. At least one language needs a name.
text.<lang>.nameThe spell’s name: one line of 1 to 100 characters, or null.
text.<lang>.castingConditionWhen a reaction is taken, or another condition on the casting time: one line of 1 to 500 characters, or null.
text.<lang>.materialThe material component: one line of 1 to 500 characters, or null. Needed in at least one language when components.m is an object, and only then.
text.<lang>.descriptionThe description in Markdown, up to 20,000 characters. Write "" to keep only the reference.
text.<lang>.higherLevelsFor a spell of level 1 or more, what a higher-level slot does, in Markdown (1 to 20,000 characters), or null. Always null for a cantrip.
text.<lang>.cantripUpgradeFor a cantrip, how it grows with the character’s level, in Markdown (1 to 20,000 characters), or null. Always null for a spell of level 1 or more.
referenceWhere the full text is, such as a book and page: one line of 1 to 100 characters, or null.
tagsYour own labels, such as a campaign: at most 10, each of 1 to 30 characters with no spaces at either end, no two alike once case and accents are ignored; [] for none. Tags are the same in every language.
createdAtWhen the spell was written: a date and time in RFC 3339, such as 2026-10-25T12:00:00Z.
updatedAtWhen it last changed, in the same form: the same as createdAt for a new spell.

Allowed values

These fields take only the keys below, written exactly as here, in English whatever the language of the text. The words after a key are how Locanda shows it.

Schools

Used in: school

  • abjuration – Abjuration
  • conjuration – Conjuration
  • divination – Divination
  • enchantment – Enchantment
  • evocation – Evocation
  • illusion – Illusion
  • necromancy – Necromancy
  • transmutation – Transmutation

Classes

Used in: classes

  • bard – Bard
  • cleric – Cleric
  • druid – Druid
  • paladin – Paladin
  • ranger – Ranger
  • sorcerer – Sorcerer
  • warlock – Warlock
  • wizard – Wizard

Casting time units

Used in: castingTime.unit

  • action
  • bonus-action
  • reaction
  • minute
  • hour

Range types

Used in: range.type

  • self – Self
  • touch – Touch
  • ranged – Ranged
  • sight – Sight
  • unlimited – Unlimited
  • special – Special

Distance units

Used in: range.unit, range.area.unit

  • feet
  • miles

Area shapes

Used in: range.area.shape

  • cone – Cone
  • cube – Cube
  • cylinder – Cylinder
  • emanation – Emanation
  • line – Line
  • sphere – Sphere

Duration types

Used in: duration.type

  • instantaneous – Instantaneous
  • timed – A set time
  • until-dispelled – Until dispelled
  • until-dispelled-or-triggered – Until dispelled or triggered
  • special – Special

Duration units

Used in: duration.unit

  • round – Rounds
  • minute – Minutes
  • hour – Hours
  • day – Days

Damage types

Used in: damageTypes

  • acid – Acid
  • bludgeoning – Bludgeoning
  • cold – Cold
  • fire – Fire
  • force – Force
  • lightning – Lightning
  • necrotic – Necrotic
  • piercing – Piercing
  • poison – Poison
  • psychic – Psychic
  • radiant – Radiant
  • slashing – Slashing
  • thunder – Thunder

Saving throw abilities

Used in: savingThrows

  • str – Strength
  • dex – Dexterity
  • con – Constitution
  • int – Intelligence
  • wis – Wisdom
  • cha – Charisma

Text languages

Used in: text

  • en – English
  • it – Italiano

JSON Schemas

To check a file before you import it, or to give a script or an AI assistant the exact rules, download the three JSON Schemas (draft 2020-12):

Load all three into the same validator, then validate the file against urn:locanda:schema:export:1: the schemas refer to each other by these urn: ids, not by web address. The format keywords are for information only, as a pattern checks the same fields. With Ajv, for example:

import Ajv2020 from 'ajv/dist/2020.js';

const ajv = new Ajv2020({ allErrors: true, validateFormats: false });
ajv.addSchema([exportSchema, customSpellSchema, spellSchema]);
const valid = ajv.validate('urn:locanda:schema:export:1', file);
console.log(valid ? 'Valid' : ajv.errors);

A few rules are checked only by Locanda when it imports: a name in at least one language; higher-level text only on spells of level 1 or more, and cantrip text only on cantrips; material text only with a material component; real calendar dates; no two tags alike; and each id only once in the file.

A prompt for an AI assistant

To turn your own notes into a file, give an AI assistant this prompt with the three schemas and the example file attached, then paste your notes after it. Check what it returns: assistants make mistakes.

I will paste notes about spells I wrote for my own game. Turn them into one Locanda custom spells file that follows the attached JSON Schemas (export.schema.json, custom-spell.schema.json and spell.schema.json) and looks like the attached example file.

Rules:
- The file is one JSON object with "format": "locanda", "schemaVersion": 1, "exportedAt" set to the current time in RFC 3339 (UTC), "appVersion": "0.0.0", "dataVersion": "custom", "spellbooks": [] and "customSpells" with one object per spell.
- Give every spell a new random id: "custom:" followed by a lowercase version 4 UUID.
- Use only the English enum keys the schemas allow (schools, classes, units, range types, area shapes, damage types, abilities), even if my notes are in another language.
- Put the words of each spell under "text", in the language of my notes ("en" for English, "it" for Italian). Write null for what my notes do not say. Write descriptions in Markdown, without HTML.
- Give distances in feet.
- Set "createdAt" and "updatedAt" to the same time as "exportedAt", "tags" to [] and "reference" to null, unless my notes give them.
- Do not invent spells, rules or text that are not in my notes. If something the schemas require is missing or unclear, ask me instead of guessing.

Reply with the JSON file only.

My notes:

Before you import

  • Every spell has a new id of its own (custom: and a fresh UUID), not one copied from the example.
  • Every value in the fields above is one of the allowed keys, in English, even when the text is in Italian.
  • The file passes a validator with the schemas above. Or simply import it: Locanda checks the whole file first, and if anything is wrong it adds nothing and says what to fix.

Whose spells

Put in the file only spells you wrote yourself or have the right to use, such as your group’s homebrew. Locanda does not check where the text comes from, and the file stays on your device unless you share it.