What is supported
- static HTML/JS (index.html at the root, no package.json)
- Vite (package.json with vite)
- Next.js (package.json with next; output "standalone" is added when missing)
- Python with Flask or FastAPI (requirements.txt or pyproject.toml, and the app in app.py or main.py when there is more than one file at the root)
Detection happens in your browser before anything is uploaded, so an unsupported project is refused in a second rather than after a long upload. The same rules run again in the builder, so what the page accepts is what the build accepts.
The one thing that catches most people
Send the folder that IS the project, not the folder that contains it. If your index.html or package.json is one level down from what you sent, OurTools unwraps up to five nested single folders looking for it, and then gives up. A zip of a folder of a folder is the same situation.
What each refusal means
No index.html, package.json, requirements.txt or pyproject.toml at the root
Nothing at the top level of what you sent says what kind of app this is.
- The usual cause is sending the folder that contains your project rather than the project itself. If your files are one level down, send that folder instead.
- A zip of a folder of a folder is the same problem, and OurTools unwraps up to five of those before giving up.
- If you exported a built site, the folder holding index.html is the one to send.
A package.json that lists neither next nor vite
There is a package.json, so this is a Node project, but nothing in it says how to build the app.
- Add vite or next as a dependency, whichever you are actually using.
- If the app is plain HTML, CSS and JavaScript with no build step, remove package.json and send the folder with index.html at its root. A package.json is what makes OurTools look for a build.
- Other frameworks are not supported yet. Building to static files and sending those works today.
A package.json that is not valid JSON
The file could not be parsed, so nothing could be read from it.
- A trailing comma is the usual cause. Open it in an editor that shows JSON errors, or run node -e "JSON.parse(require('fs').readFileSync('package.json'))".
requirements.txt or pyproject.toml without flask or fastapi
This is a Python project, but nothing in it serves HTTP.
- Add flask or fastapi as a dependency. OurTools runs Flask with gunicorn and FastAPI with uvicorn, and reads the port from the environment for you.
- A script with no web server has nothing to serve, so there is nothing for a URL to point at.
No Python file at the root
There is a dependency file, but no .py file beside it.
- Put the file holding your Flask or FastAPI object at the top level, next to requirements.txt.
- Files named setup.py, conftest.py, manage.py, and anything starting or ending with test, are never treated as the entry.
Several Python files at the root and none named conventionally
More than one .py file sits at the root, so which one starts the app is a guess.
- Name the file holding your Flask or FastAPI object app.py. OurTools also accepts main.py, application.py, server.py, asgi.py and wsgi.py, in that order of preference.
- The other files can stay where they are. Having config.py, models.py and helpers beside your app is normal and supported.
- Whichever file is chosen, it has to expose the Flask or FastAPI instance as a module level variable called app.
What is left out of your upload, always
Dependencies, build output, caches and environment files are removed before anything is sent, and the size limit applies to what is left. That means a large project folder is usually fine: node_modules, .venv, dist, build, .next, coverage and log files never leave your machine.
.env files are the exception that is not negotiable. They are removed even if your own .gitignore says to keep them, because an upload is not the way to get secrets into an app. Put those in Settings, where they are encrypted before they are stored. .env.example and its siblings are kept, since they carry no values.
Your lockfile is always kept, even if it is gitignored. Without it the build resolves whatever the registry offers that minute, which is how an app that worked yesterday fails today.
Your own .gitignore is read and honoured on top of that list. A .ourtoolsignore works the same way and wins where the two disagree.
Limits
- 200 MB for the packed upload, measured after the exclusions above, not before.
- 50 MB for any single file, and 50,000 files in total.
- 25 MB for a single request through the proxy once the app is running. Fine for a spreadsheet upload, wrong for a video pipeline.
- Environment variable names up to 128 characters, values up to 32 KB. Names are uppercase letters, digits and underscores, and cannot start with a digit.
- These names are set by the platform and cannot be used:
PORT,K_SERVICE,K_REVISION,K_CONFIGURATION,GOOGLE_APPLICATION_CREDENTIALS. Anything startingOURTOOLS_is reserved too.
Things worth knowing before you deploy
Your app sleeps when nobody is using it and wakes on the next request. A background thread, a scheduler or a queue worker running inside your web process will not run reliably, because there is no process between requests. Work that has to happen on a timetable belongs outside the app.
Write to a database, not to disk. The filesystem is not kept between requests or between deploys. Bring a connection string for a database you already have, or ask for a managed one and it is wired in for you.
Your app does not need any login code. Sign-in is checked before a request reaches it, and the verified person arrives in headers your app can read. Any X-OurTools- header a caller sends is deleted before yours are set.
Python apps must expose the app object as app at module level. It is imported during the build, so a name that does not exist fails the deploy with a real traceback rather than serving errors later.