Custom Code Guide - ItsChrisOfficial/perchance-bot-workshop GitHub Wiki
Custom Code Guide
customCode is a character-level field that contains runtime JavaScript for Perchance bots. It is the most common source of export failures because it must survive two parsers: JSON parsing and JavaScript parsing.
The Dual-Parser Problem
customCode is stored as a JSON string value. This means:
- The JavaScript must be valid source code
- That source code must be properly escaped as a JSON string
- After JSON parsing unescapes it, the result must still be valid JavaScript
What goes wrong
- Raw newlines in the JSON string → JSON parse fails
- Unescaped quotes → JSON parse fails
- Template literals with
${...}not properly escaped → broken JS after parse - Hand-assembled escaping → subtle breakage that looks right but isn't
Safe Authoring Workflow
✅ Correct workflow
- Write JavaScript as normal source code (not inside JSON)
- Place that source into the
customCodefield in a JavaScript/Python object - Serialize the entire export object with a JSON serializer (
JSON.stringify) - Parse the resulting JSON back to verify it round-trips
- Extract
customCodeand run a JavaScript syntax check
❌ Wrong workflow
- Write JSON by hand
- Paste raw JavaScript into the JSON string
- Guess the escaping
- Assume it will import
This is how broken exports get shipped.
Example (Node.js)
const fs = require('fs');
// 1. Load the export template
const exportObj = JSON.parse(fs.readFileSync('template.json', 'utf8'));
// 2. Write customCode as normal JavaScript
const customCode = `
(() => {
oc.thread.on("MessageAdded", function() {
console.log("Message added!");
});
})();
`;
// 3. Assign it to the character row
exportObj.data.data[0].rows[0].customCode = customCode;
// 4. Serialize with JSON.stringify (handles escaping automatically)
const output = JSON.stringify(exportObj, null, 2);
// 5. Verify round-trip
const parsed = JSON.parse(output);
const extracted = parsed.data.data[0].rows[0].customCode;
// 6. Syntax-check the extracted code
new Function(extracted); // throws if invalid JS
fs.writeFileSync('my-bot-export.json', output);
Perchance Runtime Environment
Custom code runs in the Perchance oc (object context) environment.
Common oc APIs
// Events
oc.thread.on("MessageAdded", function() { ... });
oc.thread.on("MessageDeleted", function() { ... });
// Thread messages
oc.thread.messages.push({ content: "...", author: "system" });
oc.thread.messages.splice(index, count);
// Character properties
oc.character.name = "Bot Name";
oc.character.roleInstruction = "...";
// Text generation
oc.generateText({ prompt: "..." });
// Image generation
oc.textToImage({ prompt: "..." });
// Rendering pipeline
oc.messageRenderingPipeline.push({ ... });
// UI windows
oc.window.show();
oc.window.hide();
// Shortcut buttons (runtime)
oc.thread.shortcutButtons = [ ... ];
Custom data initialization
Always guard against missing nested objects:
oc.thread.customData ||= {};
oc.character.customData ||= {};
Best Practices
Structure
- Wrap larger systems in an IIFE to avoid polluting global scope:
(() => {
// bot code here
})();
- If globals are needed for
onclickhandlers, make them deliberate and namespaced - Guard against duplicate initialization
Message objects in code
When creating messages at runtime, use fully-formed message objects:
oc.thread.messages.push({
content: "System notification",
author: "system",
expectsReply: false,
hiddenFrom: ["ai"]
});
Stability
- Do not assume optional nested objects already exist
- Avoid brittle DOM dependencies unless the feature truly requires iframe UI
- Do not leave debug-only logs or dead handlers in release code
- Do not leave
TODOplaceholders in release output
Common customCode Failures
| Failure | Cause | Fix |
|---|---|---|
| JSON parse error | Raw newlines or unescaped quotes in string | Use JSON.stringify to serialize |
| JS syntax error after parse | Unterminated template literal or string | Check JS syntax after extraction |
customCode is not a string |
Stored as object/number instead of string | Ensure field is a string type |
| Duplicate initialization | Code runs multiple times without guard | Add initialization guards |
| Broken escaping | Hand-assembled JSON string | Always serialize programmatically |
Validation
After building your customCode:
-
Run the export validator:
node scripts/validate-perchance-export.js /path/to/export.json -
The validator checks that
customCode:- Is a string type
- Contains valid JavaScript syntax (parsed after JSON extraction)
See Validation and Testing for the full validation workflow.