Welcome to the
Shellcraft Forms Creator
The free, open-source alternative to Typeform. Build beautiful, multi-step forms with no code.
The free, open-source alternative to Typeform. Build beautiful, multi-step forms with no code.
Click to Interact
This script will collect your form responses in a Google Sheet. It also supports file uploads directly to a folder in your Google Drive.
function doPost(e) {
try {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = JSON.parse(e.postData.contents);
var formName = data.formName || "Shellcraft Form Submissions";
// --- File Upload Handling ---
if (data.fileData) {
var folder;
var folders = DriveApp.getFoldersByName(formName);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(formName);
folder.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
}
var contentType = data.fileType;
var decodedData = Utilities.base64Decode(data.fileData.split(',')[1]);
var blob = Utilities.newBlob(decodedData, contentType, data.fileName);
var file = folder.createFile(blob);
data[data.fileQuestionId] = file.getUrl(); // Add file URL to data object
// Clean up file data to avoid writing it to the sheet
delete data.fileData;
delete data.fileName;
delete data.fileType;
delete data.fileQuestionId;
}
// --- Write Data to Sheet ---
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var headerMap = {};
headers.forEach(function(header, i) {
if (header !== "") {
headerMap[header] = i;
}
});
var newRow = new Array(headers.length).fill('');
for (var key in data) {
if (headerMap.hasOwnProperty(key)) {
newRow[headerMap[key]] = data[key];
} else {
// Add new header if it doesn't exist
var newHeaderIndex = headers.length;
sheet.getRange(1, newHeaderIndex + 1).setValue(key);
headers.push(key);
headerMap[key] = newHeaderIndex;
while(newRow.length < headers.length) { newRow.push(''); }
newRow[newHeaderIndex] = data[key];
}
}
sheet.appendRow(newRow);
return ContentService.createTextOutput(JSON.stringify({ "result": "success" })).setMimeType(ContentService.MimeType.JSON);
} catch (error) {
return ContentService.createTextOutput(JSON.stringify({ "result": "error", "error": error.toString() })).setMimeType(ContentService.MimeType.JSON);
}
}
You can use an external AI (like ChatGPT, Gemini, etc.) to quickly generate the correct JSON format from a simple list of questions or a description.
Parse the following text into a JSON object representing a complete form configuration. The object can have top-level keys like "title", "description", "timeEstimate", "successTitle", and "successDescription". It MUST have a "questions" key containing an array of question objects.
Each question object in the array must have "id", "title", "type", and "required" (boolean).
- "id" should be a unique camelCase version of the title.
- "type" can be 'text', 'long-text', 'email', 'radio', 'checkbox', 'dropdown', 'ratingScale', 'yes-no', 'datetime', or 'file-upload'. Note: Use 'radio' for single choice and 'checkbox' for multi-choice. Use 'datetime' for date/time questions.
- For 'datetime' questions, you can add "datetimeFormat" which can be 'datetime-local', 'date', or 'time'. Default is 'datetime-local'.
- For 'yes-no' questions, you can add "positiveText" (e.g. "Agree") and "negativeText" (e.g. "Disagree").
- For types 'radio', 'checkbox', and 'dropdown', include an "options" property with the choices as a single string separated by newlines.
- For types 'text', 'long-text', and 'email', you can optionally add a "placeholder" string.
- For types 'radio' and 'checkbox', you can optionally add "numberingStyle" which can be 'abc' or '123'. Default to 'abc'.
- Optionally add a "description" string to any question for more context.
- Assume all questions are required unless specified otherwise.
Output ONLY the raw JSON object, nothing else.
Here is the form I want:
[PASTE YOUR QUESTIONS OR DESCRIPTION HERE]