You want to move your Airtable Bases to SeaTable and are afraid of the effort? Don't worry! Even bases with complicated data structures and many thousands of records can be transferred to SeaTable with little effort.
We have developed a migration script for the transfer of Airtable data to SeaTable. This transfers all data in an Airtable base - tables, columns, data records - to a SeaTable base in one go. After the migration, you can continue in SeaTable with the data that you last used in Airtable. After executing the script, only a few adjustments are necessary.
The script can be used by everyone - technicians as well as non-technicians. However, due to limitations on the part of the Airtable API, the procedure is unfortunately not highly automated. Some manual specifications are required to make the script operational. In this article we explain how to do that.
How to migrate a Base from Airtable to SeaTable
1. create base
Create a new base in SeaTable. The name of the SeaTable Base does not have to be the same as the name of the Airtable Base you want to import. You can choose the name freely.
2. insert script
In the new base, open the script management by clicking on the script icon in the base header at the top right. Then select Add script. The migration script is written in the Python programming language. Therefore, select Python.
Now select the following code block, copy it to the clipboard and then paste it into the left window of the script editor:
## Parameterize the script # SeaTable - Destination server_url = 'https://cloud.seatable.io' api_token = '...' # Add the API token of the SeaTable base # See https://seatable.io/docs/en/seatable-api/erzeugen-eines-api-tokens/ # for more information on how to create a SeaTable API token # Airtable - Source airtable_personal_access_token = '...' # Add a Personal Access Token (PAT) # PATs are 82-character strings and begin with "pat" (e.g. 'pat544WlSOq6T4Fvv.5710af6611aedbf28493c38084163494e02b24f078cf2d62f07105982a82a64d') # See https://support.airtable.com/docs/creating-personal-access-tokens/ # for more information on how to create a PAT in Airtable airtable_base_id = '...' # Add the Base ID of the Airtable base # Base IDs are alphanumeric strings and begin with "app" (e.g. 'appRfA3qspH3EJUnV') # See https://support.airtable.com/docs/finding-airtable-ids/ # for more information on where to find the id of an Airtable base table_names = ['...', '...'] # Add the names of all tables in the Airtable base, i.e. ['table 1', 'table 2'] # The names must be enclosed in '' and comma-separated first_columns = [ ('...', '...'), ('...', '...'), ] # Specify the names of the first columns in every table of the Airtable base # Use the format ('table_name', 'first_column_name'), i.e. ('table 1', 'ID') # The table and column name must be enclosed in '' and comma-separated links = [ ] # Specify the links between the tables in the Airtable base # Use the format ('table_name', 'column_name', 'other_table_name'), i.e., ('table 1', 'link to table 2', 'table 2') # The table and column names must be enclosed in '' and comma-separated # If the Airtable base contains no link columns, just leave the brackets empty mode = 'import-header' # Specify the run-mode of the script, two options: 'import-header' and 'import-rows' # Run 'import-header' first to create the data structure in the SeaTable base # Run 'import-rows' to import all the rows ## No more edits required beyond this row import sys from seatable_api import Base, AirtableConvertor # from airtable_importer_settings import server_url, api_token, # airtable_api_key, airtable_base_id, table_names, first_columns, links def get_convertor(): base = Base(api_token, server_url) base.auth() convertor = AirtableConvertor( airtable_api_key=airtable_personal_access_token, airtable_base_id=airtable_base_id, base=base, table_names=table_names, first_columns=first_columns, left=left, ) return convertor def import_header(): convertor = get_convertor() convertor.convert_metadata() def import_rows(): convertor = get_convertor() convertor.convert_data() if mode == 'import-header': import_header() elif mode == 'import-rows': import_rows() else: print('The mode is not properly specified.') ## End script ##
Don't worry if you don't understand the code you have just copied. We will explain it. The comments in the code should help a little with interpretation. Comments are all rows with a leading hash sign ('#'). These rows are not taken into account when the script is executed, i.e. you can change, delete or add comments without affecting the functionality of the script.
Unlike the comments, the indentations in the code are very important when running the script. Please do not change them.
3. specify SeaTable server URL and API token of the base.
Under the comment SeaTable - Destination, add the SeaTable server URL and specify the API token. If you are using SeaTable Cloud, then the pre-filled URL "https://cloud.seatable.io" is correct. If you are using a different SeaTable server, then enter its URL. (The URL must always be entered with https:// or http://.) The URL and token must be enclosed in single quotes.
This is a sample configuration when using SeaTable Cloud:
4. enter Airtable Personal Access Token and Base ID
Add below the comment Airtable - Source the Airtable Personal Access Token (PAT) and the Airtable Base IDboth values again in single quotation marks. The PAT should have the authorization data.records:read
have.
This is how it should look then:
5. enter table and column names
Now tell the script which tables you want to import from the Airtable Base. Do this in the row "table_names". Add the names of the tables in the square brackets - each enclosed by a single quotation mark and separated by a comma.
For a base with two tables "table 1" and "table 2", the row must then look like this, for example:
If your Airtable Base has more than two tables, simply extend the list in brackets. If you do not want to transfer all the tables of a base, simply omit the names of the tables that you do not want to copy.
Due to a limitation of the Airtable API, you must also specify the names of the first columns in the tables. This is done in the row "first_columns" or the following rows.
For the airtable base with the two tables "table 1" and "table 2" it could then look like this:
6. define link columns
In order for SeaTable to import the data properly, one more step is required: specifying the link columns in Airtable Base.
The variable "links" is intended for this purpose. For example, if the "link to table 2" column in the "table 1" table represents a link to the "table 2" table, the script should be parameterized as follows:
You need to specify each link column pair only once. You do not need to specify the link in both directions.
If the airtable base does not receive any link columns, then you can leave the square bracket empty:
7. import tables and columns
The script can be executed in two modes: "import-header" and "import-rows". First, the script must always be executed in the "import-header" mode. In this mode, the tables and columns are created in SeaTable Base and 10 test rows are imported.
The execution mode is defined via the variable of the same name:
Now run the script by clicking Run Script. During the execution you can see the executed steps on the right side in the editor. In the background you can also see how the tables and columns are created.
8. check test lines
Now check whether
- all tables and all columns have been imported,
- the correct first column has been created in all tables and
- the link columns are correct.
Since the column types of Airtable and SeaTable are not completely congruent, some column types in the SeaTable Base will differ from those in the Airtable Base. The following table shows how the column types in Airtable are transferred to SeaTable.
Airtable column type | Import to SeaTable column type |
---|---|
Attachment | File |
Autonumber | Number (conversion to a car number column possible) |
Barcode | Text or number |
Button | - (no migration possible) |
Checkbox | Checkbox |
Count | Number |
Created time | Date |
Currency | Number |
Date | Date |
Duration | Number (conversion to a column of type duration possible) |
Formula | Date, text or number |
Last modified by | Text |
Last modified time | Date |
Link to another record | Link to other entries |
Long text | Formatted text |
Lookup | Date, text or number |
Multiple select | Multiple selection (No migration of unused options) |
Number | Number |
Percent | Number |
Phone number | Text or number |
Rating | Number (conversion to a column of type Rating possible) |
Rollup | Date, text or number |
Single line text | Text |
Single select | Text (conversion to a single selection column possible) |
User | Text |
URL | Text |
If something is not correct, check the entries in steps 5 and 6 and run the script again in "import-header" mode. You can run the script as often as you like.
9. import all records
If the verification in step 8. is to your satisfaction, then all records can be transferred.
In the script, change the mode from "import-header" to "import-rows":
Run the script again by clicking Run Script. Again you can watch the execution of the script on the right side and see the activities in the background.
Now you should see all records from the Airtable Base in the new SeaTable Base. Congratulations, the dataset migration is complete!
If you are not satisfied with the final result, delete all tables and start again at step 7.
10. complete migration
The migration script has transferred all data. Some manual rework may still be necessary now.
The migration script does not transfer formula columns as formula columns, but creates text columns and inserts the formula results into them. I.e. you still have to add formula columns manually in the SeaTable Base. The same applies to columns of type Count, Lookup and Rollup. Button columns are not transferred at all.
Since the order of the columns cannot be retrieved via the Airtable API, this information is unfortunately not available when creating the columns in SeaTable. If you want to restore the original order, then you have to do it manually.
Views from the Airtable Base are not transferred. You must create views yourself. The same applies to automations, interfaces and scripts.
FAQ area
Does the script change data in my airtable base?
No. The script only reads your airtable base. The data in the airtable base is not changed.
What do I do if I receive an error message?
An error message on first execution is not at all unlikely. There are plenty of reasons for this, e.g. an incorrect token, an incorrect sequence in the specification of the links, typing errors, missing commas or incorrect indentation. A single error can lead to script execution being aborted.
If you receive an error message, this is generally not a problem. No data can be lost. Proceed as follows to find the cause of the error:
- In most cases, the error message will give you a hint where to find the error. Follow it, correct the entered parameters and run the script again. An indentation error, for example, indicates that the indentation does not conform to Python syntax.
- If the error message is incomprehensible, check all entries for correctness and try again.
- If none of the tips help, get help in the SeaTable Forum.
Can columns in the airtable base be overlooked?
Yes, this can happen with columns that contain only a few entries. In the "import-header" mode the script checks the first 100 rows of the airtable base. If a column does not contain an entry in these rows , the Airtable API does not return this column and the corresponding column is not created in SeaTable. Consequently, it will not be included in the migration of all records.
Can Airtable Bases of any size be migrated?
In principle, yes. However, the procedure presented in this article with the integrated Python editor is subject to a maximum runtime limit of 15 minutes, i.e. scripts that run for longer than 15 minutes are aborted. In practice, this is sufficient for the vast majority of bases. However, this limit can play a role for bases with large/many file attachments.
Is it possible to import tables into an existing base?
The script does not require a base to be empty. So you can run the script even in a base with existing, filled tables. The only thing you should avoid is duplicating table names.